├── Conic_constant_derivation.png ├── dist2.m ├── exGaussian.m ├── exGaussian_fit.m ├── exGaussian2D_fill.m ├── scatterRayPlaneIntersect.m ├── lensmakers_formula.m ├── example11.m ├── humanCSF.m ├── aspheric.m ├── rodrigues_rot.m ├── example12.m ├── surface_sag.m ├── example5.m ├── ellipse_fit.m ├── coslens.m ├── exGaussian_fit2D.m ├── ellipse_draw.m ├── frenlens.m ├── example15.m ├── example8.m ├── cylinder_intersection.m ├── example6.m ├── example14.m ├── asphlens.m ├── example4.m ├── example9.m ├── Surface.m ├── quadric_intersection.m ├── example10.m ├── hist2.m ├── cone_intersection.m ├── ellipse_fill.m ├── example16.m ├── example13.m ├── example3.m ├── example1.m ├── example7.m ├── conic_intersection.m ├── CylinderLens.m ├── README.md ├── ConeLens_old.m ├── GeneralLens.m ├── AsphericLens.m ├── example2.m ├── ConeLens.m ├── Retina.m ├── Screen.m ├── lens_dims.m ├── export_stl.m ├── Aperture.m ├── Plane.m ├── Lens.m ├── refrindx.m ├── Sellmeier.glass.refr ├── Eye.m ├── hatchfill.m ├── draw_lens_engineering.m ├── Bench.m └── FresnelLens.m /Conic_constant_derivation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caiuspetronius/Optometrika/HEAD/Conic_constant_derivation.png -------------------------------------------------------------------------------- /dist2.m: -------------------------------------------------------------------------------- 1 | function d = dist2( l, r0, e, surf ) rend = r0 + l * e; % the ray's end d = ( rend( :, 1 ) - surf.funch( rend( :, 2 ), rend( :, 3 ), surf.funca, 0 ) ).^2; end -------------------------------------------------------------------------------- /exGaussian.m: -------------------------------------------------------------------------------- 1 | function f = exGaussian( x, mu, sigma, lambda ) 2 | % EXGAUSSIAN implements exponentially modified Gaussian distribution 3 | % http://en.wikipedia.org/wiki/Exponentially_modified_Gaussian_distribution 4 | 5 | f = lambda * exp( ( sigma * lambda )^2 / 2 - ( x - mu ) * lambda ) .* normcdf( ( x - mu ) / sigma - sigma * lambda ); 6 | if sum( ~isfinite( f ) ) ~= 0 % the distribution is very close to a Gaussian, replace with a Gaussian 7 | f = 1 / ( sigma * sqrt( 2 * pi ) ) * exp( -( x - mu ).^2 / ( 2 * sigma^2 ) ); 8 | end -------------------------------------------------------------------------------- /exGaussian_fit.m: -------------------------------------------------------------------------------- 1 | function [ mu, sigma, lambda ] = exGaussian_fit( x ) 2 | % EXGAUSSIAN_FIT fits exponentially modified Gaussian distribution to x 3 | % http://en.wikipedia.org/wiki/Exponentially_modified_Gaussian_distribution 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | m = mean( x ); 9 | s = std( x ); 10 | g = skewness( x ); 11 | sn = 1; 12 | if g < 0 13 | g = -g; 14 | sn = -1; 15 | end 16 | 17 | t = ( g / 2 )^(1/3); 18 | mu = m - s * t; 19 | sigma = s * sqrt( 1 - t^2 ); 20 | lambda = sn / ( s * t ); % save sign into the sign of lambda -------------------------------------------------------------------------------- /exGaussian2D_fill.m: -------------------------------------------------------------------------------- 1 | function f = exGaussian2D_fill( X, Y, Mu, Std, Lambda, angle ) 2 | % EXGAUSSIAN implements exponentially modified Gaussian distribution 3 | % http://en.wikipedia.org/wiki/Exponentially_modified_Gaussian_distribution 4 | % extending it to 2D 5 | % 6 | % Copyright: Yury Petrov, 2016 7 | % 8 | 9 | R = [ X(:) Y(:) ]; 10 | Rt = R * [ cos( -angle ), sin( -angle ); -sin( -angle ), cos( -angle ) ]; 11 | if Lambda( 2 ) < 0 12 | Rt( :, 2 ) = -Rt( :, 2 ); 13 | Lambda( 2 ) = - Lambda( 2 ); 14 | end 15 | f = exGaussian( Rt( :, 1 ), Mu( 1 ), Std( 1 ), Lambda( 1 ) ) .* exGaussian( Rt( :, 2 ), Mu( 2 ), Std( 2 ), Lambda( 2 ) ); 16 | f = f / sum( f ); 17 | f = reshape( f, size( X ) ); -------------------------------------------------------------------------------- /scatterRayPlaneIntersect.m: -------------------------------------------------------------------------------- 1 | function [ v, rinter ] = scatterRayPlaneIntersect( x, r, n, i, plr0, pln ) % scatter (var) of rays intersections with a plane with norm pln and origin plr plr = plr0 + x * pln; % the tested plane position cnt = size( n, 1 ); d = dot( repmat( pln, cnt, 1 ), repmat( plr, cnt, 1 ) - r, 2 ) ./ ... dot( n, repmat( pln, cnt, 1 ), 2 ); % calculate intersection vectors rinter = r + repmat( d, 1, 3 ) .* n; % intersection vectors v = sum( i .* sqrt( sum( ( rinter - repmat( mean( rinter ), cnt, 1 ) ).^2, 2 ) ) ); % / ( 1 + x^2 ); % angular size of the bundle intersection with the plane % v = sum( i .* sum( ( rinter - repmat( mean( rinter ), cnt, 1 ) ).^2, 2 ) ); % / ( 1 + x^2 ); % angular size of the bundle intersection with the plane end -------------------------------------------------------------------------------- /lensmakers_formula.m: -------------------------------------------------------------------------------- 1 | function [ P, nrefr ] = lensmakers_formula( Rf, Rb, wavelength, glass ) 2 | % LENSMAKERS_FORMULA calculates refractive index and approximate the lens power near 3 | % its axis using the Lensmaker's formula 4 | % 5 | % INPUT: 6 | % Rf - front lens surface curvature radius, meters 7 | % Rb - back lens surface curvature radius, meters 8 | % wavelenght - of lihght, meters 9 | % glass - optical medium of the lens, see refrindx.m for the list 10 | % 11 | % OUTPUT: 12 | % P - lens power in diopters 13 | % nrefr - lens refractive index for the given wavelength 14 | % 15 | % Copyright: Yury Petrov, 2016 16 | % 17 | 18 | nrefr = refrindx( wavelength, glass ); 19 | P = 1000 * ( nrefr - 1 ) * ( 1 / Rf - 1 / Rb + ( nrefr - 1 ) * h / ( nrefr * Rf * Rb ) ); % Diopters 20 | 21 | end 22 | 23 | -------------------------------------------------------------------------------- /example11.m: -------------------------------------------------------------------------------- 1 | function example11() 2 | % 3 | % Demonstrates ray tracing for rays originating inside the human eye 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | bench = Bench; 9 | 10 | eye = Eye; 11 | eye.rotate( [ 0 0 1 ], pi ); % make the eye face along the positive x-axis direction 12 | eye.remove( 1 ); % remove the retina surface, otherwise it will block the light even though it is physically behind the source 13 | bench.append( eye ); 14 | 15 | screen = Screen( [ 20 0 0 ], 20, 20, 500, 500 ); 16 | bench.append( screen ); 17 | 18 | rays_in = Rays( 100, 'source', [ 0 0 0 ], [ 1 0 0 ], 0.5, 'hexagonal', 'vitreous' ); % make sure that the rays start with the right refractive indices, 'vitreous' in this case 19 | 20 | rays_out = bench.trace( rays_in ); 21 | bench.draw( rays_out, 'rays' ); % draw results using dotted lines, the asterisks mark where rays intersect surfaces. -------------------------------------------------------------------------------- /humanCSF.m: -------------------------------------------------------------------------------- 1 | function csf = humanCSF( f ) 2 | % 3 | % This function approximates human contrast sensitivity function (CSF), 4 | % which is definced as the reciprocal of the threshold contrast. The 5 | % analytical fit is from Watson AB and Ahumada AJ,Jr. "A standard model for 6 | % foveal detection of spatial contrast." J Vis. 2005; 5 (9): 717?740. 7 | % The analytical form is given by the difference of hyperbolic secants 8 | % describing high and low frequency parts of CSF and defined by 5 parameters: 9 | % 10 | % CSF( f; f0,f1,a,p ) = gain * ( sech((f/f0)^p) ? a * sech(f/f1) ) 11 | % 12 | 13 | gain = 373.08; 14 | f0 = 4.1726; 15 | f1 = 1.3625; 16 | a = 0.8493; 17 | p = 0.7786; 18 | % beta = 2.4081; % summation over channels metric, not used in CSF fit 19 | % sigma = 0.6273; % summation over periphery of the fovea, not used 20 | 21 | csf = gain * ( sech( ( f / f0 ).^p ) - a * sech( f / f1 ) ); -------------------------------------------------------------------------------- /aspheric.m: -------------------------------------------------------------------------------- 1 | function s = aspheric( pars, r ) 2 | % 3 | % ASPHERIC describes a symmetric aspherical lens with sag given by 4 | % s = r^2 / R / ( 1 + sqrt( 1 - ( k + 1 ) * r^2 / R^2 ) ) + a(1) * r^2 + 5 | % a(2) * r^4 + ... + a(n) * r^2n 6 | % 7 | % INPUT: 8 | % pars: with the following parameters 9 | % pars(1:2) = R - two radii of curvature 10 | % pars(3) = k - conic constant 11 | % pars(4:end) = a - higher aspherics values 12 | % r - radii 13 | % 14 | % OUTPUT: 15 | % s - sag values corresponding to r values 16 | % 17 | % Copyright: Yury Petrov, 2016 18 | % 19 | 20 | R = pars( 1 ); % radius of curvature along y-axis 21 | k = pars( 3 ); 22 | a = pars( 4:end ); 23 | r2 = r.^2; 24 | s = ( r2 / R ) ./ ( 1 + sqrt( 1 - ( k + 1 ) * r2 ./ R^2 ) ); 25 | s( ~isreal( s ) ) = 1e+20; % prevent complex values 26 | for i = 1 : length( a ) 27 | s = s + a( i ) * r2.^i; 28 | end -------------------------------------------------------------------------------- /rodrigues_rot.m: -------------------------------------------------------------------------------- 1 | function v_rot = rodrigues_rot( v, k, theta ) 2 | % RODRIQUES_ROT - Rotates array of 3D vectors by an angle theta about vector k. 3 | % Direction is determined by the right-hand (screw) rule. 4 | % 5 | % Syntax: v_rot = rodrigues_rot( v, k, theta ) 6 | % 7 | % INPUT: 8 | % v - n x 3 array of vectors to be rotated 9 | % k - 1 x 3 rotation axis 10 | % theta - rotation angle, radians 11 | % OUTPUT: 12 | % v_rot - Array of rotated vectors. 13 | % 14 | % Copyright: Yury Petrov, 2016 15 | % 16 | 17 | [ m, n ] = size( v ); 18 | if n ~= 3 19 | error( 'Input vector is not 3 dimensional!' ); 20 | end 21 | if size( k, 2 ) ~= 3 22 | error( 'Rotation axis is not 3 dimensional!' ); 23 | end 24 | 25 | k = k / norm( k ); % normalize rotation axis 26 | k = repmat( k, m, 1 ); 27 | v_rot = v .* cos( theta ) + cross( k, v, 2 ) .* sin( theta ) + k .* repmat( dot( k, v, 2 ), 1, 3 ) .* ( 1 - cos( theta ) ); 28 | end -------------------------------------------------------------------------------- /example12.m: -------------------------------------------------------------------------------- 1 | function example12() 2 | % 3 | % Draw a lens and determine its front surface, back surface, and total 4 | % height. Make an animated gif of the lens and an engineering drawing of 5 | % the lens. 6 | % 7 | % Copyright: Yury Petrov, 2016 8 | % 9 | 10 | Df = 40; % front diameters 11 | Db = 50; % back diameters 12 | Rf = -50; % front radius of curvature 13 | Rb = 200; % back radius of curvature 14 | kf = -3; % front conic constant 15 | kb = 0; % back conic constant 16 | h = 14; % lens height at the edge 17 | 18 | [ ht, hf, hb, V ] = lens_dims( Df, Db, Rf, Rb, kf, kb, h, [], [], 1 ); 19 | fprintf( 'Front surface height: %.3f\n', hf ); 20 | fprintf( 'Back surface height: %.3f\n', hb ); 21 | fprintf( 'Total lens height in the center: %.3f\n', ht ); 22 | fprintf( 'Lens volume: %.3f\n', V ); 23 | fprintf( 'Animated gif saved in lens_dims.gif\n' ); 24 | f = draw_lens_engineering( [ 0, hf + hb + h ], [ Df Db ], [ Rf Rb ], [ kf kb ], [], 'zeonex', [], 'AR', 'flange', 2, 2, 'Make my lens really beautiful'); 25 | fprintf( 'Engineering drawing saved in draw_lens_engineering.pdf\n' ); -------------------------------------------------------------------------------- /surface_sag.m: -------------------------------------------------------------------------------- 1 | function s = surface_sag( D, R, k, a ) 2 | % 3 | % calculate maximum sag of the surface defined by diameter D, radius R, 4 | % and conic constant k, and, optionally, by aspheric polynomial terms a 5 | % 6 | % Copyright: Yury Petrov, 2016 7 | % 8 | 9 | if nargin < 4 || isempty( a ) || sum( a.^2 ) == 0 % no polynomial coefficients 10 | if k == -1 % paraboloid 11 | s = D^2 / ( 8 * R ); 12 | else % spheroids, hyperboloids 13 | if isinf( R ) 14 | s = 0; 15 | else 16 | x = R / ( 1 + k ); 17 | det = 1 - ( 1 + k ) * D^2 / ( 4 * R^2 ); 18 | if det < 0 19 | s = Inf; 20 | else 21 | s = sign( R ) * abs( x * ( 1 - sqrt( det ) ) ); 22 | end 23 | end 24 | end 25 | else 26 | if size( a, 1 ) > size( a, 2 ) 27 | a = a'; 28 | end 29 | % r = linspace( 0, D/2, 10000 ); % densly sample the lens radius 30 | % s = max( abs( aspheric( [ R k a ], r ) ) ); 31 | s = aspheric( [ R k a ], D/2 ); 32 | end 33 | -------------------------------------------------------------------------------- /example5.m: -------------------------------------------------------------------------------- 1 | function example5() 2 | % 3 | % test planar mirrors 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | % create a container for optical elements (Bench class) 9 | bench = Bench; 10 | 11 | % add optical elements in the order they are encountered by light rays 12 | 13 | % planar mirror 14 | mirror1 = Plane( [ 60 0 0 ], 40, 40, { 'air' 'mirror' } ); % pay attention to the glass order here, it defines the mirror orientation! 15 | mirror1.rotate( [ 0 0 1 ], -pi / 4 ); 16 | bench.append( mirror1 ); 17 | 18 | % planar mirror 19 | mirror2 = Plane( [ 60 50 0 ], 40, 40, { 'mirror' 'air' } ); % pay attention to the glass order here! 20 | mirror2.rotate( [ 0 0 1 ], -pi / 4 ); 21 | bench.append( mirror2 ); 22 | 23 | % screen 24 | screen = Screen( [ 100 50 0 ], 20, 15, 128 * 20/15, 128 ); 25 | bench.append( screen ); 26 | 27 | % create collimated rays 28 | nrays = 500; 29 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 58, 'hexagonal' ); 30 | 31 | fprintf( 'Tracing rays...\n' ); 32 | rays_through = bench.trace( rays_in ); 33 | 34 | % draw bench elements and draw rays as arrows 35 | bench.draw( rays_through, 'lines', [], 2 ); % display everything, scale arrow length 2x 36 | 37 | end 38 | -------------------------------------------------------------------------------- /ellipse_fit.m: -------------------------------------------------------------------------------- 1 | function [ x0, cv, ax, ang ] = ellipse_fit( x ) 2 | % fits an ellipse to cover a cloud of dots in 2D defined by x 3 | % 4 | % INPUT: 5 | % - x : n x 2 or n x 3 matrix of datapoints 6 | % 7 | % OUTPUT: 8 | % - x0 : 1 x 2 vector of the ellipse center 9 | % - ax : 1 x 2 vector of the ellipse principal half axes 10 | % - ang : the angle of the ellipsoid rotation (longest half-axis off the 11 | % x-axis, radians) 12 | % 13 | % Copyright: Yury Petrov, 2016 14 | % 15 | 16 | ax = []; 17 | ang = []; 18 | 19 | if nargin < 2 20 | flag = 0; 21 | end 22 | 23 | x0 = mean( x ); 24 | cv = cov( x ); % get covariance matrix 25 | 26 | if flag == 0 % use x moments 27 | if size( x, 2 ) == 2 28 | d = sqrt( ( cv(1,1) - cv(2,2) )^2 / 4 + cv(1,2)^2 ); % discriminant 29 | t = ( cv(1,1) + cv(2,2) ) / 2; 30 | ax = 2 * sqrt( [ t + d; t - d ] ); % eigenvalues: [ max min ] 31 | ang = atan2( d - ( cv(1,1) - cv(2,2) ) / 2, cv(1,2) ); % positions the largest eigenvector along the x-axis 32 | else % do PCA 33 | [ ~, S, V ] = svd( cv ); 34 | ax = sqrt( diag( S ) ); 35 | ax = ax( 1 : 2 ); % choose the two largest eigenvalues 36 | ang = atan2( V( 2, 1 ), V( 3, 1 ) ); 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /coslens.m: -------------------------------------------------------------------------------- 1 | function x = coslens( y, z, args, flag ) 2 | % To trace through a general lens profile one needs to create a function, 3 | % which takes two arguments ( y, z ) defining a position in the lens 4 | % plane, an arbitrary number of additional arguments provided in the cell 5 | % array args, and, finally, a flag argument. On flag == 0 the function 6 | % should return the lens height x for the given position ( y, z ). Otherwise, 7 | % the function should return the lens normal at this position. By convention, 8 | % the normal should point along the x-axis, i.e. in the same general 9 | % direction as the traced ray. 10 | % 11 | % COSLENS defines a radially symmetric cosine profile used in example4. 12 | % the first two input arguments are coordinates in the lens plane, the 13 | % third argument is a cell array holding the lens height argv{1}, and the 14 | % cosine period argv{2}. 15 | 16 | r = sqrt( y.^2 + z.^2 ); 17 | if flag == 0 18 | x = args{1} * ( 1 - cos( 2 * pi / args{2} * r ) ); 19 | else 20 | c = 1 ./ sqrt( 1 + ( 2 * pi * args{1} / args{2} .* sin( 2 * pi / args{2} * r ) ).^2 ); 21 | s = sqrt( 1 - c.^2 ); 22 | th = atan2( z, y ); 23 | x = -sign( args{ 1 } ) * [ -sign( args{ 1 } ) * c, s .* cos( th ), s .* sin( th ) ]; 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /exGaussian_fit2D.m: -------------------------------------------------------------------------------- 1 | function [ mu, sigma, lambda, ang ] = exGaussian_fit2D( x ) 2 | % EXGAUSSIAN_FIT2D fits exponentially modified Gaussian distribution to x 3 | % in 2D 4 | % http://en.wikipedia.org/wiki/Exponentially_modified_Gaussian_distribution 5 | % 6 | % Copyright: Yury Petrov, 2016 7 | % 8 | 9 | cv = cov( x ); 10 | d = sqrt( ( cv(1,1) - cv(2,2) )^2 / 4 + cv(1,2)^2 ); % discriminant 11 | t = ( cv(1,1) - cv(2,2) ) / 2; 12 | V = [ cv(1,2), -cv(1,2); d - t, d + t ]; % eigenvectors 13 | V( :, 1 ) = V( :, 1 ) / norm( V( :, 1 ) ); % normalize 14 | V( :, 2 ) = V( :, 2 ) / norm( V( :, 2 ) ); 15 | xt = x * V; % rotate data into eigevectors RF, the largest eigenvector is along the x-axis 16 | ang = atan2( V( 2, 1 ), V( 1, 1 ) ); % positions the largest eigenvector along the x-axis 17 | 18 | % fit each dimension separately 19 | [ mu( 1 ), sigma( 1 ), lambda( 1 ) ] = exGaussian_fit( xt( :, 1 ) ); 20 | [ mu( 2 ), sigma( 2 ), lambda( 2 ) ] = exGaussian_fit( xt( :, 2 ) ); 21 | 22 | if lambda( 1 ) < 0 23 | ang = ang - pi; 24 | xt = -xt; % rotate by Pi 25 | [ mu( 1 ), sigma( 1 ), lambda( 1 ) ] = exGaussian_fit( xt( :, 1 ) ); 26 | [ mu( 2 ), sigma( 2 ), lambda( 2 ) ] = exGaussian_fit( xt( :, 2 ) ); 27 | 28 | if lambda( 2 ) < 0 % the sign of lambda remains and indicates the flip 29 | xt( :, 2 ) = -xt( :, 2 ); % reverse y sign 30 | mu( 2 ) = exGaussian_fit( xt( :, 2 ) ); 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /ellipse_draw.m: -------------------------------------------------------------------------------- 1 | function ellipse_draw( x0, cv, ax, angle, nvert, color ) 2 | % Draw an ellips defined by its center, half-axes, and angle 3 | % 4 | % INPUT: 5 | % - x0 : 1 x 2 vector of the ellipse center 6 | % - cv : 2 x 2 covariance matrix, or 1 x 3 vector of (1,1), (1,2), (2,2) elements of cv 7 | % - ax : 1 x 2 vector of the ellipse principal half axes 8 | % - ang : the angle of the ellipsoid rotation (longest half-axis off the 9 | % x-axis, radians) 10 | % 11 | % Copyright: Yury Petrov, 2016 12 | % 13 | 14 | if nargin < 6 15 | color = [ 0 0 1 ]; 16 | end 17 | if nargin < 5 18 | nvert = 40; 19 | end 20 | if nargin < 4 21 | angle = []; 22 | end 23 | if nargin < 3 24 | ax = []; 25 | end 26 | 27 | nvert = ceil( nvert / 2 ); 28 | 29 | if size( cv, 1 ) == 2 && size( cv, 2 ) == 2 30 | cv = [ cv( 1, 1 ) cv( 1, 2 ) cv( 2, 2 ) ]; 31 | end 32 | 33 | if isempty( ax ) || isempty( angle ) 34 | d = sqrt( ( cv(1) - cv(3) )^2 / 4 + cv(2)^2 ); % discriminant 35 | t = ( cv(1) + cv(3) ) / 2; 36 | ax = 2 * sqrt( [ t + d; t - d ] ); % eigenvalues: [ max min ] 37 | angle = atan2( d - ( cv(1) - cv(3) ) / 2, cv(2) ); % positions the largest eigenvector along the x-axis 38 | end 39 | 40 | x = linspace( -ax(1), ax(1), nvert )'; 41 | y = ax(2) * sqrt( 1 - ( x / ax(1) ).^2 ); 42 | x = [ x; flipud( x ) ]; 43 | y = [ y; -flipud( y ) ]; 44 | r = [ x y ]; 45 | 46 | r = r * [ cos( angle ), sin( angle ); -sin( angle ), cos( angle ) ]; 47 | r = r + repmat( x0, size( r, 1 ), 1 ); 48 | patch( r( :, 1 ), r( :, 2 ), color ); -------------------------------------------------------------------------------- /frenlens.m: -------------------------------------------------------------------------------- 1 | function [ x, yp ] = frenlens( y, Fr ) 2 | % FRENLENS defines a radially symmetric Fresnel profile. 3 | % the first input argument is the profile radii, the 4 | % second argument is a FresnelLens object. 5 | % 6 | % Copyright: Yury Petrov, 2017 7 | % 8 | 9 | x = []; 10 | yp = []; 11 | for i = 1 : Fr.ncones % loop over cones 12 | if i == 1 13 | if y(1) == 0 14 | radin = 0; 15 | else 16 | 2 * Fr.rad( 1, 1 ) - Fr.rad( 2, 1 ); % take the inner radius assuming the same step 17 | end 18 | else 19 | radin = Fr.rad( i - 1, 1 ); 20 | end 21 | 22 | if isempty( Fr.vx ) || Fr.R( i, 1 ) == 0 || isinf( Fr.k( i ) ) % cone surface 23 | z( 1, : ) = Fr.sag( i ); 24 | z( 2, : ) = Fr.sag( i ) + ( Fr.rad( i, 2 ) - radin ) / tan( Fr.the( i, 1 ) ); 25 | else % quadric surface 26 | r = y( y >= radin & y < Fr.rad( i, 2 ) ); 27 | a = 1 + Fr.k( i ); 28 | r2 = r.^2 / Fr.R( i )^2; 29 | if abs( a ) < 1e-10 % paraboloid, special case 30 | z = r2 * Fr.R( i, 1 ) / 2; 31 | else 32 | z = Fr.R( i ) / a * ( 1 - sqrt( 1 - a * r2 ) ); 33 | end 34 | z = z + Fr.sag( i ) + Fr.vx( i ); % add sag and quadric vertex displacement 35 | end 36 | 37 | if abs( Fr.the( i, 1 ) - pi/2 ) > 1e-10 && ( sign( ( z( 2 ) - z( 1 ) ) / ( Fr.rad( i, 2 ) - radin ) ) ~= sign( tan( Fr.the( i, 1 ) ) ) ) 38 | z = flipud( z ); 39 | end 40 | x = [ x z ]; 41 | yp = [ yp r ]; 42 | end 43 | -------------------------------------------------------------------------------- /example15.m: -------------------------------------------------------------------------------- 1 | function example15() 2 | % 3 | % Simulate a hexagonal array of spherical microlenses 4 | % 5 | % Copyright: Yury Petrov, 2017 6 | % 7 | 8 | R = 1; % spherical lens radius 9 | nLenses = 19; 10 | D = 10 * R; % substrate plate diameter 11 | h = 0.5; % substrate plate height 12 | 13 | % create a container for optical elements (Bench class) 14 | bench = Bench; 15 | 16 | % get hexagonal locations from the Rays function with the 'hexagonal' pattern 17 | locs = Rays( nLenses, 'collimated', [ 10 0 0 ], [ 1 0 0 ], 7 * R, 'hexagonal' ); 18 | 19 | for i = 1 : length( locs.r ) 20 | lens = Lens( locs.r( i, : ), 2 * R - 1e-6, R, 0, { 'air' 'pmma' } ); % a half-sphere 21 | bench.append( lens ); 22 | end 23 | 24 | % add the screen-side substrate plate for the microlenses 25 | plane = Plane( [ 10 + R + h 0 0 ], D + R, { 'pmma' 'air' } ); 26 | bench.append( plane ); 27 | 28 | % screen 29 | screen = Screen( [ 12.3 0 0 ], D, D, 500, 500 ); 30 | bench.append( screen ); 31 | 32 | % create some rays 33 | nrays = 300; 34 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 10 * R, 'hexagonal' ); 35 | rays_out = bench.trace( rays_in, 0 ); % must use 0 as the last argument to consider rays missing a given microlens as candidates for hitting another one 36 | bench.draw( rays_out, 'rays' ); 37 | 38 | % get a high-res image on the screen 39 | rays_in = Rays( 100 * nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 10 * R, 'hexagonal' ); 40 | rays_out = bench.trace( rays_in, 0 ); 41 | figure( 'Name', 'Image on the screen', 'NumberTitle', 'Off' ); 42 | imshow( screen.image, [] ); 43 | 44 | -------------------------------------------------------------------------------- /example8.m: -------------------------------------------------------------------------------- 1 | function example8() 2 | % 3 | % test a lens with polynomial aspheric terms 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | % create a container for optical elements (Bench class) 9 | bench = Bench; 10 | 11 | % add optical elements in the order they are encountered by light rays 12 | 13 | % front lens surface 14 | lens1 = AsphericLens( [ 40 0 0 ], 31, 80, 0, [ 0 -1e-05 3e-07 ], { 'air' 'pmma' } ); 15 | bench.append( lens1 ); 16 | 17 | % back lens surface 18 | lens2 = AsphericLens( [ 52.5 0 0 ], 31, -10, -2, [ 0 -2e-05 2e-07 ], { 'pmma' 'air' } ); % aspheric surface 19 | bench.append( lens2 ); 20 | 21 | % screen 22 | screen = Screen( [ 70 0 0 ], 3, 3, 256, 256 ); 23 | bench.append( screen ); 24 | 25 | % create collimated rays with some slant 26 | nrays = 500; 27 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 30, 'hexagonal' ); 28 | 29 | tic; 30 | fprintf( 'Tracing rays... ' ); 31 | rays_through = bench.trace( rays_in ); % repeat to get the min spread rays 32 | 33 | % draw bench elements and draw rays as arrows 34 | bench.draw( rays_through ); % display everything, the other draw option is 'lines' 35 | 36 | % get the screen image in high resolution 37 | nrays = 10000; 38 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 30, 'hexagonal' ); 39 | bench.trace( rays_in ); 40 | figure( 'Name', 'Image on the screen', 'NumberTitle', 'Off' ); 41 | imshow( screen.image, [] ); 42 | 43 | toc; 44 | 45 | draw_lens_engineering( [ 40 52.5 ], [ 31 31 ], [ 80 -10 ], [ 0 -2 ], [ 0 -1e-05 3e-07; 0 -2e-05 2e-07 ]', 'pmma' ); 46 | fprintf( 'Engineering drawing saved in draw_lens_engineering.pdf\n' ); 47 | end 48 | -------------------------------------------------------------------------------- /cylinder_intersection.m: -------------------------------------------------------------------------------- 1 | function [ in, rin ] = cylinder_intersection( r_in, e, sD, sh, surf ) % c2 = e( :, 2 ).^2 + e( :, 3 ).^2; % d^2 coefficients % c1 = 2 * ( e( :, 2 ) .* r_in( :, 2 ) + e( :, 3 ) .* r_in( :, 3 ) ); % d^1 coefficients % c0 = r_in( :, 2 ).^2 + r_in( :, 3 ).^2 - ( sD / 2 ).^2; % d^0 coefficients % % % solve c2 * d^2 + c1 * d + c0 = 0 for d % D = c1.^2 - 4 * c2 .* c0; % D( D < -1e-12 ) = Inf; % suppress negative values (no intersection with the half-cone) to avoid imaginary values % D( D < 0 ) = 0; % remove round off negative Ds % D = sqrt( D ); % dm = 0.5 * ( -c1 - D ) ./ c2; % dp = 0.5 * ( -c1 + D ) ./ c2; a2 = ( sD/2 ).^2; [ ~, ~, dm, dp ] = quadric_intersection( 0, a2, a2, 1, r_in, e ); % orient the reference frame along the z direction instead of the x direction % discount the previous intersection, where the ray originates if isa( surf, 'Screen' ) || isa( surf, 'Retina' ) % allow negative ray directions to use with virtual images dm( abs( dm ) < 1e-12 ) = realmax; dp( abs( dp ) < 1e-12 ) = realmax; else dm( dm < 1e-12 ) = realmax; dp( dp < 1e-12 ) = realmax; end % form the intersection vectors, determine which is the first intersection, which is the second fst = dm < dp; % don't count the ray's origin on a surface; snd = ~fst; % first try the first intersection rin1 = r_in + repmat( dm .* fst + dp .* snd, 1, 3 ) .* e; % use the closest intersection with the surface in1 = rin1( :, 1 ) >= 0 & rin1( :, 1 ) <= sh; % then try the second intersection rin2 = r_in + repmat( dm .* snd + dp .* fst, 1, 3 ) .* e; % use the closest intersection with the surface in2 = rin2( :, 1 ) >= 0 & rin2( :, 1 ) <= sh; in = in1 | in2; % use either intersection rin = rin1; rin( ~in1 & in2, : ) = rin2( ~in1 & in2, : ); % only use second intersection if the first is invalid end -------------------------------------------------------------------------------- /example6.m: -------------------------------------------------------------------------------- 1 | function example6() 2 | % 3 | % test planar and parabolic mirrors (a refractor telescope) 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | % create a container for optical elements (Bench class) 9 | bench = Bench; 10 | 11 | % add optical elements in the order they are encountered by light rays 12 | 13 | % back surface of the planar mirror 14 | mirror1 = Plane( [ 45 0 0 ], 8, 4, { 'mirror' 'air' } ); % pay attention to the glass order here! 15 | mirror1.rotate( [ 0 0 1 ], -pi / 4 ); 16 | bench.append( mirror1 ); 17 | 18 | % parabolic mirror 19 | mirror2 = Lens( [ 100 0 0 ], 40, -120, -1, { 'air' 'mirror' } ); % pay attention to the glass order here! 20 | bench.append( mirror2 ); 21 | 22 | % front sufrace of the planar mirror 23 | mirror3 = Plane( [ 45 0 0 ], 8, 4, { 'mirror' 'air' } ); % pay attention to the glass order here! 24 | mirror3.rotate( [ 0 0 1 ], -pi / 4 ); 25 | bench.append( mirror3 ); 26 | 27 | % screen 28 | screen = Screen( [ 45 -5 0 ], 1, 1, 512, 512 ); 29 | screen.rotate( [ 0 0 1 ], -pi/2 ); 30 | bench.append( screen ); 31 | 32 | % create collimated rays 33 | nrays = 500; 34 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 58, 'hexagonal' ); 35 | 36 | fprintf( 'Tracing rays...\n' ); 37 | rays_through = bench.trace( rays_in, 0 ); % the second parameter set to 0 enables ray tracing for rays missing some elmeents on the bench 38 | 39 | % draw bench elements and draw rays as arrows 40 | bench.draw( rays_through, 'arrows', [], [ 3 0 2 1 .1 ] ); % display everything, specify arrow length for each bench element 41 | 42 | % get the screen image in high resolution 43 | nrays = 10000; 44 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 58, 'hexagonal' ); 45 | bench.trace( rays_in, 0 ); 46 | figure( 'Name', 'Image on the screen', 'NumberTitle', 'Off' ); 47 | imshow( screen.image, [] ); 48 | 49 | end 50 | -------------------------------------------------------------------------------- /example14.m: -------------------------------------------------------------------------------- 1 | function example14() 2 | % 3 | % Test astigmatic lens surfaces. The same as example1, but with an astigmatic front 4 | % surface of the lens. The z (vertical dimension) curvature is changed here 5 | % to produce vertical defocus. 6 | % 7 | % Copyright: Yury Petrov, 2017 8 | % 9 | 10 | % create a container for optical elements (Bench class) 11 | bench = Bench; 12 | 13 | % add optical elements in the order they are encountered by light rays 14 | 15 | % front lens surface 16 | % parabolic surface with two different radii of curvature along the y and z 17 | % dimensions (astigmatism). In this case the conic constant k applies to 18 | % the y dimension, k value along the z dimension will be determined from the 19 | % astigmatism Ry / Rz 20 | lens1 = Lens( [ 40 0 0 ], 58, [ 40 45 ], -1, { 'air' 'bk7' } ); 21 | % back lens surface 22 | lens2 = Lens( [ 60 0 0 ], 58, -70, -3, { 'bk7' 'air' } ); % concave hyperbolic surface 23 | bench.append( { lens1, lens2 } ); 24 | 25 | % screen 26 | screen = Screen( [ 102.76 0 0 ], 10, 10, 512, 512 ); % screen position based on the tightest convergence focus in example1 27 | bench.append( screen ); 28 | 29 | %bench.rotate( [ 0 0 1 ], 0.15 ); 30 | 31 | % create some rays 32 | nrays = 100; 33 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 50, 'hexagonal' ); 34 | 35 | rays_through = bench.trace( rays_in ); 36 | 37 | % draw rays for the original focus (without the astigmatism) 38 | rays_through = bench.trace( rays_in ); % repeat to get the min spread rays 39 | bench.draw( rays_through, 'lines' ); % display everything, the other draw option is 'lines' 40 | 41 | % get the screen image in high resolution 42 | nrays = 10000; 43 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 50, 'hexagonal' ); 44 | bench.trace( rays_in ); 45 | figure( 'Name', 'Image on the screen', 'NumberTitle', 'Off' ); 46 | imshow( screen.image, [] ); 47 | 48 | end 49 | 50 | -------------------------------------------------------------------------------- /asphlens.m: -------------------------------------------------------------------------------- 1 | function x = asphlens( y, z, args, flag ) 2 | % ASPHLENS defines a commonly used radially symmetric aspherical profile. 3 | % the first two input arguments are coordinates in the lens plane, the 4 | % third argument is a vector holding the aspheric parameters: 5 | % args(1:2) = Ry and Rz - radii of curvature 6 | % args(3) = k - conic constant along y 7 | % args(4:end) = a - higher aspherics values 8 | % On flag == 0 the function 9 | % should return the lens height x for the given position ( y, z ). Otherwise, 10 | % the function should return the lens normal at this position. By convention, 11 | % the normal should point along the x-axis, i.e. in the same general 12 | % direction as the traced ray. 13 | % 14 | % Copyright: Yury Petrov, 2017 15 | % 16 | 17 | R = args(1); % Ry 18 | sc = args(1) / args(2); % astigmatism: Ry / Rz 19 | k = args(3); 20 | r = sqrt( y.^2 + z.^2 ); 21 | r2yz = ( y.^2 + z.^2 ) / R^2; 22 | if flag == 0 23 | x = aspheric( args, sqrt( y.^2 + ( sc * z ).^2 ) ); % aspheric surface profile 24 | else 25 | if k == -1 % parabola, special case 26 | c = 1 ./ sqrt( 1 + r2yz ); 27 | s = sqrt( 1 - c.^2 ); 28 | else 29 | s = sqrt( r2yz ) ./ sqrt( 1 - k * r2yz ); 30 | c = sqrt( 1 - s.^2 ); 31 | end 32 | tg = sign( R ) * s ./ c; 33 | for i = 4 : length( args ) % add higher aspherical derivatives to the conic derivative 34 | ii = i - 3; 35 | tg = tg + args( i ) * 2 * ii .* r.^( 2 * ii - 1 ); 36 | end 37 | c = 1 ./ sqrt( 1 + tg.^2 ); % component along the ray direction, always positive 38 | s = -sign( tg ) .* sqrt( 1 - c.^2 ); % sign of the transverse component determined by the local convave/convex shape 39 | th = atan2( z, y ); % rotation angle to bring r into XZ plane 40 | x = [ c, s .* cos( th ), sc * s .* sin( th ) ]; % make normal sign positive wrt ray, scale z-component according to the one-form transformation 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /example4.m: -------------------------------------------------------------------------------- 1 | function example4() 2 | % 3 | % test a ring lens with the cosine surface profile defined in coslens.m 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | % create a container for optical elements (Bench class) 9 | bench = Bench; 10 | 11 | % add optical elements in the order they are encountered by light rays 12 | 13 | % aperture 14 | % aper = Aperture( [ 40 0 0 ], [ 55 80 ] ); 15 | aper = Aperture( [ 40 0 0 ], [ 30 50 80 80 ] ); % rectangular aperture 16 | bench.append( aper ); 17 | 18 | % front lens surface 19 | lens1 = GeneralLens( [ 40 0 0 ], [ 20 58 ], 'coslens', { 'air' 'bk7' }, 10, 116 ); % cosine ring surface 20 | % back lens surface 21 | lens2 = Lens( [ 58 0 0 ], [ 20 58 ], -50, -3, { 'bk7' 'air' } ); % concave hyperbolic ring surface 22 | bench.append( { lens1, lens2 } ); 23 | 24 | % prism front surface 25 | prism1 = Plane( [ 65 0 0 ], [ 10, 30 ], { 'air' 'acrylic' } ); 26 | prism1.rotate( [ 0 0 1 ], 0.3 ); % rotate the front prism plane by .3 radians about z-axis 27 | % prism back surface 28 | prism2 = Plane( [ 75 0 0 ], [ 10, 30 ], { 'acrylic' 'air' } ); 29 | prism2.rotate( [ 0 1 0 ], 0.2 ); % rotate the back prism plane by .2 radians about y-axis 30 | bench.append( { prism1, prism2 } ); 31 | 32 | % screen 33 | screen = Screen( [ 93 0 0 ], 20, 15, 128 * 20/15, 128 ); 34 | bench.append( screen ); 35 | 36 | % create collimated rays with some slant 37 | nrays = 500; 38 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 -0.1 0 ], 68, 'hexagonal' ); 39 | 40 | tic; 41 | fprintf( 'Tracing rays... ' ); 42 | rays_through = bench.trace( rays_in ); 43 | 44 | % draw bench elements and draw rays as arrows 45 | bench.draw( rays_through, 'arrows' ); % display everything, the other draw option is 'lines' 46 | 47 | % get the screen image in high resolution 48 | nrays = 10000; 49 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 -0.1 0 ], 58, 'hexagonal' ); 50 | bench.trace( rays_in, 0 ); 51 | figure( 'Name', 'Image on the screen', 'NumberTitle', 'Off' ); 52 | imshow( kron( screen.image, ones( 3 ) ), [] ); 53 | 54 | toc; 55 | end 56 | -------------------------------------------------------------------------------- /example9.m: -------------------------------------------------------------------------------- 1 | function example9() 2 | % 3 | % test cone mirrors 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | fov = 160; % FOV, degrees 9 | fov = fov / 180 * pi; 10 | D = [ 62 50 33 ]; % mirror diameters 11 | LD = 47; % lens diameter 12 | 13 | d1 = ( D(1) - D(2) ) / 2; 14 | d2 = ( D(2) - D(3) ) / 2; 15 | d3 = ( D(1) - D(3) ) / 2; 16 | c = tan( fov / 4 ); 17 | r = roots( [ d3/2, c * ( d3/2 - d2 ), d3/2, c * ( d1 - d3/2 ) ] ); 18 | cang1 = -atan( r( imag( r ) == 0 ) ); %-fov/6; 19 | cang2 = cang1 - fov/4; 20 | h1 = -( D(1) - D(2) ) / 2 / tan( cang1 ); % depth of the first mirror 21 | h2 = -( D(2) - D(3) ) / 2 / tan( cang2 ); % depth of the second mirror 22 | 23 | % create a container for optical elements (Bench class) 24 | bench = Bench; 25 | 26 | % add optical elements in the order they are encountered by light rays 27 | 28 | block = Aperture( [ 30 0 0 ], [ 0 LD ] ); 29 | bench.append( block ); 30 | 31 | % first cone mirror surface 32 | mirror1 = ConeLens( [ 30 0 0 ], D(1), h1, cang1, { 'air' 'mirror' } ); 33 | bench.append( mirror1 ); 34 | 35 | % second cone mirror surface 36 | mirror2 = ConeLens( [ 30 + h1 0 0 ], D(2), h2, cang2, { 'air' 'mirror' } ); 37 | bench.append( mirror2 ); 38 | 39 | % screen 40 | screen = Screen( [ 30 + h1 + h2 0 0 ], D(1), D(1), 256, 256 ); 41 | bench.append( screen ); 42 | 43 | % create collimated rays 44 | nrays = 200; 45 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], D(1) - 0.01, 'linear' ); 46 | y = rays_in.r( :, 2 ); 47 | z = rays_in.r( :, 3 ); 48 | rays_in.color( abs( z ) < 5 & y > LD/2, 2 ) = 0; % 49 | rays_in.color( abs( z ) < 5 & y > LD/2 & y <= LD/2 + ( D(1) - LD )/4, 1 ) = 1; % mark some rays red 50 | rays_in.color( abs( z ) < 5 & y > LD/2 + ( D(1) - LD )/4, 3 ) = 1; % mark some rays blue 51 | 52 | rays_through = bench.trace( rays_in, 0 ); % trace the rays, enable tracing rays that miss some bench elements by setting the second input parameter to 0 53 | 54 | % draw bench elements and draw rays as arrows 55 | bench.draw( rays_through, 'lines' ); % display everything, the other draw option is 'lines' 56 | 57 | end 58 | -------------------------------------------------------------------------------- /Surface.m: -------------------------------------------------------------------------------- 1 | classdef Surface < handle 2 | % SURFACE base class for optic elements 3 | % 4 | % Member functions: 5 | % 6 | % b = a.copy - copy surface a to surface b 7 | % 8 | % rotate( rot_axis, rot_angle ) - rotate the surface by rot_angle 9 | % (radians) about the 1x3 rotation axis, inherited by derived classes. 10 | % 11 | % Copyright: Yury Petrov, 2016 12 | % 13 | 14 | properties % public access 15 | r = [ 0 0 0 ]; % location vector 16 | R = []; % radius of the tangent sphere 17 | k = []; % conic constant 18 | glass = { 'air' 'air' }; % material in front and behind the surface 19 | end 20 | 21 | properties ( SetAccess = private ) 22 | rotax = [ 1 0 0 ]; % rotation axis for rotation transformation 23 | rotang = 0; % rotation angle about the rotax, radians 24 | n = [ 1 0 0 ]; % orientation (normal) vector, can be set by a rotate function only 25 | end 26 | 27 | methods 28 | 29 | function b = copy( self ) 30 | b = feval( class( self ) ); 31 | p = properties( self ); 32 | for i = 1:length( p ) 33 | b.( p{ i } ) = self.( p{ i } ); 34 | end 35 | end 36 | 37 | function rotate( self, rot_axis, rot_angle ) 38 | if abs( rot_angle ) > pi 39 | error( 'Rotation angle should be [ -pi pi ]!' ); 40 | end 41 | % rotate the normal about the rot_axis by rot_angle (radians) 42 | self.rotax = rot_axis; 43 | self.rotang = self.rotang + rot_angle; 44 | self.n = rodrigues_rot( self.n, rot_axis, rot_angle ); 45 | if abs( self.rotang ) > pi/2 46 | self.rotang = self.rotang - sign( self.rotang ) * pi; 47 | self.R = -self.R; % surface upside-down 48 | self.glass = fliplr( self.glass ); % change material ordering 49 | self.n = -self.n; % make the surface normal point along the rays 50 | end 51 | end 52 | 53 | end 54 | 55 | end -------------------------------------------------------------------------------- /quadric_intersection.m: -------------------------------------------------------------------------------- 1 | function [ r1, r2, t1, t2 ] = quadric_intersection( a2, b2, c2, s, r0, n ) 2 | % 3 | % Find intersection of a line defined by the origin vector r0 and direction 4 | % vector n with a quadric surface defined by a^2, b^2, c^2 (signs 5 | % included) and the sign of the constant 1 (s), i.e. with the equation 6 | % x^2/a^2 + y^2/b^2 + z^2/c^2 = s 7 | % https://en.wikipedia.org/wiki/Quadric 8 | % a2 = 0 and s = 0 defines a paraboloid (elliptical or hyperbolic) 9 | % a2 = 0 and s = 1 defines a cylinder (elliptical, hyperbolic, or parabolic) 10 | % 11 | % INPUT: 12 | % - a2, b2, c2 : quadric semiaxes. Sign of b2 should be positive 13 | % - s : the constant of the quadric as given by x^2/a^2 + y^2/b^2 + z^2/c^2 = s 14 | % - r0 : rays origins, n x 3 matrix 15 | % - n : ray directions, n x 3 matrix 16 | % 17 | % OUTPUT: 18 | % - r1, r2 : n x 3 intersection matrices 19 | % - t1, t2 : n distances to the intersections 20 | % 21 | % Copyright: Yury Petrov, 2017 22 | % 23 | 24 | if b2 <= 0 25 | error( 'b2 must be > 0!' ); 26 | end 27 | 28 | x0 = r0( :, 1 ); 29 | y0 = r0( :, 2 ); 30 | z0 = r0( :, 3 ); 31 | x02 = x0.^2; 32 | y02 = y0.^2; 33 | z02 = z0.^2; 34 | 35 | j = n( :, 1 ); 36 | k = n( :, 2 ); 37 | l = n( :, 3 ); 38 | j2 = j.^2; 39 | k2 = k.^2; 40 | l2 = l.^2; 41 | 42 | if a2 == 0 43 | if s == 1 % cylinder 44 | a = k2 ./ b2 + l2 ./ c2; 45 | b = 2 * ( y0 .* k ./ b2 + z0 .* l ./ c2 ); 46 | c = y02 ./ b2 + z02 ./ c2 - 1; 47 | elseif s == 0 % paraboloid 48 | a = k2 ./ b2 + l2 ./ c2; 49 | b = 2 * ( y0 .* k ./ b2 + z0 .* l ./ c2 - j/2 ); 50 | c = y02 ./ b2 + z02 ./ c2 - x0; 51 | end 52 | else % ellipsoids, hyperboloids, cones 53 | a = j2 ./ a2 + k2 ./ b2 + l2 ./ c2; 54 | b = 2 * ( x0 .* j ./ a2 + y0 .* k ./ b2 + z0 .* l ./ c2 ); 55 | c = x02 ./ a2 + y02 ./ b2 + z02 ./ c2 - s; 56 | end 57 | 58 | D = b.^2 - 4 * a .* c; 59 | D( D < -1e-12 ) = Inf; % suppress negative values to avoid imaginary values 60 | D( D < 0 ) = 0; % remove round off negative Ds 61 | t1 = 0.5 * ( -b - sqrt( D ) ) ./ a; 62 | t2 = 0.5 * ( -b + sqrt( D ) ) ./ a; 63 | 64 | r1 = r0 + n .* t1; 65 | r2 = r0 + n .* t2; 66 | 67 | -------------------------------------------------------------------------------- /example10.m: -------------------------------------------------------------------------------- 1 | function example10() 2 | % 3 | % test cylinder and cone surfaces with double refraction 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | D = 40; % cylinder diameter 9 | h = 20; % cylinder height 10 | 11 | % create a container for optical elements (Bench class) 12 | bench = Bench; 13 | 14 | % add optical elements in the order they are encountered by light rays 15 | 16 | % cylindrical mirror surface with an elliptic cylinder 17 | % the elliptic cylinder is defined by [ Dy Dz ] pair in the function below 18 | mirror = CylinderLens( [ 20 0 0 ], [ D, 1.5*D ], h, { 'air', 'mirror' } ); 19 | bench.append( mirror ); 20 | 21 | % NOTE THAT THE CYLIDRICAL LENS IS DOUBLED HERE, BECAUSE RAYS 22 | % ENCOUNTER IT TWICE!!! 23 | 24 | % cylindrical lens front surface 25 | lens = CylinderLens( [ 70 0 2*h ], D, 4*h, { 'air', 'pc' } ); 26 | lens.rotate( [ 0 1 0 ], pi/2 ); 27 | bench.append( lens ); 28 | % cylindrical lens back surface 29 | lens = CylinderLens( [ 70 0 2*h ], D, 4*h, { 'air' 'pc' } ); 30 | lens.rotate( [ 0 1 0 ], pi/2 ); 31 | bench.append( lens ); 32 | 33 | % conical lens front surface 34 | % the elliptic cone is defined by [ Dy Dz ] pair in the function below 35 | lens = ConeLens( [ 110 -55 0 ], [ 0.6*D D ], 108, 0.08, { 'air', 'pmma' } ); 36 | %lens = CylinderLens( [ 110 -55 0 ], [ 0.6*D D ], 108, { 'air', 'pmma' } ); 37 | lens.rotate( [ 0 0 1 ], pi/2 ); 38 | bench.append( lens ); 39 | % conical lens back surface 40 | lens = ConeLens( [ 110 -55 0 ], [ 0.6*D D ], 108, 0.08, { 'air' 'pmma' } ); 41 | %lens = CylinderLens( [ 110 -55 0 ], [ 0.6*D D ], 108, { 'air', 'pmma' } ); 42 | lens.rotate( [ 0 0 1 ], pi/2 ); 43 | bench.append( lens ); 44 | 45 | % screen 46 | screen = Screen( [ 150 0 0 ], 3 * D, 3 * D, 256, 256 ); 47 | bench.append( screen ); 48 | 49 | % create divergent rays 50 | nrays = 100; 51 | rays_in = Rays( nrays, 'source', [ 0 0 0 ], [ 1 0 0 ], 2., 'hexagonal' ); 52 | 53 | rays_through = bench.trace( rays_in, 0 ); % trace the rays, enable tracing rays that miss some bench elements by setting the second input parameter to 0 54 | 55 | % draw bench elements and draw rays as arrows 56 | bench.draw( rays_through, 'rays' ); % display everything, the other draw option is 'lines' 57 | 58 | end 59 | -------------------------------------------------------------------------------- /hist2.m: -------------------------------------------------------------------------------- 1 | function histmat = hist2( x, y, weights, xedges, yedges ) 2 | % function histmat = hist2(x, y, xedges, yedges) 3 | % 4 | % Extract 2D histogram data containing the number of events 5 | % of [x , y] pairs that fall in each bin of the grid defined by 6 | % xedges and yedges also accounting for each pair's weight. 7 | % The edges are vectors with monotonically non-decreasing values. 8 | % 9 | %EXAMPLE 10 | % 11 | % events = 1000000; 12 | % x1 = sqrt(0.05)*randn(events,1)-0.5; x2 = sqrt(0.05)*randn(events,1)+0.5; 13 | % y1 = sqrt(0.05)*randn(events,1)+0.5; y2 = sqrt(0.05)*randn(events,1)-0.5; 14 | % x= [x1;x2]; y = [y1;y2]; 15 | % 16 | %For linearly spaced edges: 17 | % xedges = linspace(-1,1,64); yedges = linspace(-1,1,64); 18 | % histmat = hist2(x, y, xedges, yedges); 19 | % figure; pcolor(xedges,yedges,histmat'); colorbar ; axis square tight ; 20 | % 21 | %For nonlinearly spaced edges: 22 | % xedges_ = logspace(0,log10(3),64)-2; yedges_ = linspace(-1,1,64); 23 | % histmat_ = hist2(x, y, xedges_, yedges_); 24 | % figure; pcolor(xedges_,yedges_,histmat_'); colorbar ; axis square tight ; 25 | % 26 | % University of Debrecen, PET Center/Laszlo Balkay 2006 27 | % email: balkay@pet.dote.hu 28 | % 29 | % Modified by Yury Petrov to include xy pair weights, 2014 30 | % 31 | 32 | if nargin ~= 5 33 | error( 'Five input arguments are required!' ); 34 | end 35 | if any( size( x ) ~= size( y ) ) 36 | error( 'The size of the two first input vectors should be same!'); 37 | end 38 | 39 | [ ~, ~, xbin ] = histcounts( x, xedges ); 40 | [ ~, ~, ybin ] = histcounts( y, yedges ); 41 | 42 | %xbin, ybin zero for out of range values 43 | % (see the help of histc) force this event to the 44 | % first bins 45 | xbin( xbin == 0 ) = inf; 46 | ybin( ybin == 0 ) = inf; 47 | 48 | xnbin = length( xedges ); 49 | ynbin = length( yedges ); 50 | 51 | xy = xbin * ynbin + ybin; 52 | indexshift = ynbin; 53 | 54 | [ xyuni, ind ] = unique( xy ); 55 | outind = isinf( xyuni ); 56 | xyuni( outind ) = []; % remove Inf bin 57 | ind( outind ) = []; 58 | hstres = histc( xy, xyuni ); 59 | if ~isempty( weights ) 60 | hstres = hstres .* weights( ind ); % weigh the histogram 61 | end 62 | 63 | clear xy; 64 | 65 | histmat = zeros( ynbin, xnbin ); 66 | histmat( xyuni - indexshift ) = hstres; 67 | 68 | -------------------------------------------------------------------------------- /cone_intersection.m: -------------------------------------------------------------------------------- 1 | function [ in, rin ] = cone_intersection( r_in, e, rad1, rad2, sag, the, surf ) % % rad1 should be < rad2. Sag is at rad1. The cone is opening up here % % find the cone's vertex coordinates and cone slice limits minsag = sag; if abs( the - pi ) < 1e-10 vx = sag; else vx = sag - rad1 / tan( the ); end maxsag = sag + ( rad2 - rad1 ) / tan( the ); if minsag == maxsag maxsag = minsag + realmin; % make the two tiny different to avoid rays disappearing at frontoparallel surfaces elseif minsag > maxsag tmp = minsag; minsag = maxsag; maxsag = tmp; end v = [ vx 0 0 ]; % cone vertex coordinates dt = r_in - repmat( v, size( r_in, 1 ), 1 ); % rays in the reference frame of the cone vertex at [ 0 0 0 ] a2 = tan( the ).^2; % if a2 == 0 % a2; % end [ ~, ~, dm, dp ] = quadric_intersection( -1, a2, a2, 0, dt, e ); % orient the reference frame along the z direction instead of the x direction % discount the previous intersection, where the ray originates if isa( surf, 'Screen' ) || isa( surf, 'Retina' ) % allow negative ray directions to use with virtual images dm( abs( dm ) < 1e-12 ) = realmax; dp( abs( dp ) < 1e-12 ) = realmax; else dm( dm < 1e-12 ) = realmax; dp( dp < 1e-12 ) = realmax; end % form the intersection vectors, determine which is the first intersection, which is the second fst = dm < dp; snd = ~fst; % first try the first intersection rin1 = r_in + repmat( dm .* fst + dp .* snd, 1, 3 ) .* e; % use the closest intersection with the surface r2 = sum( rin1( :, 2 : 3 ).^2, 2 ); if rad1 < rad2 in1 = r2 <= rad2^2 & r2 >= rad1^2 & rin1( :, 1 ) >= minsag & rin1( :, 1 ) <= maxsag; else in1 = r2 <= rad1^2 & r2 >= rad2^2 & rin1( :, 1 ) >= minsag & rin1( :, 1 ) <= maxsag; end % then try the second intersection rin2 = r_in + repmat( dm .* snd + dp .* fst, 1, 3 ) .* e; % use the closest intersection with the surface r2 = sum( rin2( :, 2 : 3 ).^2, 2 ); if rad1 < rad2 in2 = r2 <= rad2^2 & r2 >= rad1^2 & rin2( :, 1 ) >= minsag & rin2( :, 1 ) <= maxsag; else in2 = r2 <= rad1^2 & r2 >= rad2^2 & rin2( :, 1 ) >= minsag & rin2( :, 1 ) <= maxsag; end in = in1 | in2; % use either intersection rin = rin1; rin( ~in1 & in2, : ) = rin2( ~in1 & in2, : ); % only use second intersection if the first is invalid end -------------------------------------------------------------------------------- /ellipse_fill.m: -------------------------------------------------------------------------------- 1 | function f = ellipse_fill( X, Y, x0, cv, ax, angle, hard ) 2 | % Fill in an ellips defined by its center, half-axes, and angle 3 | % 4 | % INPUT: 5 | % - x0 : 1 x 2 vector of the ellipse center 6 | % - cv : 2 x 2 covariance matrix or 1 x 3 vector of the upper triangular matrix of cv 7 | % - ax : 1 x 2 vector of the ellipse principal half axes 8 | % - ang : the angle of the ellipsoid rotation (longest half-axis off the x-axis, radians) 9 | % - hard : a float indicating the ellipse edge hardness, 1 - makes ellispe similar to a 2D Gaussian, > 20 - a hard-edged ellipse 10 | % 11 | % OUTPUT: 12 | % - f : n x 1 matrix of values, 1 inside the ellipse, 0 - outside 13 | % 14 | % Copyright: Yury Petrov, 2016 15 | % 16 | 17 | if nargin < 7 18 | hard = 100; % hard edged ellipse by default 19 | end 20 | if nargin < 6 21 | angle = []; 22 | end 23 | if nargin < 5 24 | ax = []; 25 | end 26 | 27 | if size( cv, 1 ) == 2 && size( cv, 2 ) == 2 28 | cv = [ cv( 1, 1 ) cv( 1, 2 ) cv( 2, 2 ) ]; 29 | end 30 | 31 | if isempty( ax ) || isempty( angle ) 32 | d = sqrt( ( cv(1) - cv(3) )^2 / 4 + cv(2)^2 ); % discriminant 33 | t = ( cv(1) + cv(3) ) / 2; 34 | ax = 2 * sqrt( [ t + d; t - d ] ); % eigenvalues: [ max min ] 35 | ax( ax < 0.5 ) = 0.5; % fill in ellipse dimensions less than a pixel. This is important to prevent 'tearing' of lens images 36 | angle = atan2( d - ( cv(1) - cv(3) ) / 2, cv(2) ); % positions the largest eigenvector along the x-axis 37 | end 38 | 39 | % rotation matrix 40 | M = [ cos( -angle ), sin( -angle ); -sin( -angle ), cos( -angle ) ]; 41 | 42 | R = [ X(:) Y(:) ]; 43 | Rt = R - repmat( x0, size( R, 1 ), 1 ); % move to the origin 44 | Rt = Rt * M; % rotate to align long half-axis with x-axis 45 | 46 | f = zeros( size( Rt, 1 ), 1 ); 47 | r2 = ( Rt( :, 1 ) / ax(1) ).^2 + ( Rt( :, 2 ) / ax(2) ).^2; 48 | if hard > 20 49 | ind = r2 < 2; 50 | f( ind ) = 1 ./ r2( ind ).^4; % antialiasing 51 | f( r2 <= 1 ) = 1; % hard-edged ellipse 52 | else % soft-edged ellipse 53 | ind = r2 < 1.5; 54 | f( ind ) = 0.5 * erfc( hard * ( sqrt( r2( ind ) ) - 1 ) ); 55 | end 56 | 57 | if sum( f ) == 0 % the ellipse is smaller than a pixel 58 | [ ~, mi ] = min( r2 ); 59 | f( mi ) = 1; % make the pixel closest to the center of the ellipse equal 1 60 | end 61 | 62 | f = reshape( f, size( X ) ); -------------------------------------------------------------------------------- /example16.m: -------------------------------------------------------------------------------- 1 | function example16() 2 | % 3 | % Export STL files of various lenses 4 | % 5 | % Copyright: Yury Petrov, 2018 6 | % 7 | 8 | nAngles = 100; % angular resolution of the STL export 9 | flangeHeight = 2; 10 | 11 | % ordianry lens 12 | lens1 = Lens( [ 0 0 0 ], 58, 40, -1, { 'air' 'bk7' } ); % front lens surface 13 | lens2 = Plane( [ 0 0 0 ], 58, { 'bk7' 'air' } ); % back lens surface 14 | export_stl( lens1, lens2, 15, nAngles, [ flangeHeight flangeHeight ], 'semi-planar.stl' ); 15 | 16 | % polynomially aspheric lens 17 | lens1 = AsphericLens( [ 0 0 0 ], 31, 80, 0, [ 0 -1e-05 3e-07 ], { 'air' 'pmma' } ); % front lens surface 18 | lens2 = AsphericLens( [ 0 0 0 ], 31, -10, -2, [ 0 -2e-05 2e-07 ], { 'pmma' 'air' } ); % back lens surface 19 | export_stl( lens1, lens2, 13, nAngles, [ flangeHeight flangeHeight ], 'aspheric.stl' ); 20 | 21 | % astigmatic ordinary lens 22 | lens1 = Lens( [ 0 0 0 ], 58, [ 40 45 ], -1, { 'air' 'bk7' } ); % front lens surface 23 | lens2 = Lens( [ 0 0 0 ], 58, -70, -3, { 'bk7' 'air' } ); % back lens surface 24 | export_stl( lens1, lens2, 20, nAngles, [ flangeHeight flangeHeight ], 'astigmatic.stl' ); 25 | 26 | % general (cosine) lens 27 | lens1 = GeneralLens( [ 0 0 0 ], 58, 'coslens', { 'air' 'bk7' }, 10, 116 ); % front lens surface 28 | lens2 = Lens( [ 0 0 0 ], 58, -50, -3, { 'bk7' 'air' } ); % back lens surface 29 | export_stl( lens1, lens2, 20, nAngles, [ flangeHeight flangeHeight ], 'Cosine.stl' ); 30 | 31 | % Fresnel lens 32 | % front lens surface 33 | R = 35; % lens radius of curvature 34 | k = -1; % lens aspheric constant 35 | D = 58; % lens diameter 36 | nrings = 15; % number of the Fresnel rings 37 | rads_outer = linspace( 5, D / 2, nrings ); % outer radii, the central ring's outer radius is 5 mm 38 | rads_inner = [ 0 rads_outer( 1 : end - 1 ) ]; % the corresponding inner radii 39 | d = sqrt( 1 - ( 1 + k ) * ( rads_inner / R ).^2 ); 40 | angs = pi/2 - atan( rads_inner ./ ( R * d ) ); % cone half-angle at the inner ring radius with respect to the x-axis direction 41 | angs(1) = k; % replace the angle at the center (normally, pi/2) with the central part's conic constant k 42 | pars = [ angs; ... 43 | repmat( pi - pi/8, 1, length( angs ) ); ... % wall half-angle at the outer ring radius, here less vertical by pi/8 (optional, defaults to pi) 44 | R ./ rads_outer ]; % ring's radial radius of curvature in units of the ring's outer radius (optional, defaults to Inf, i.e. to a flat conical surface) 45 | sags = zeros( 1, length( rads_inner ) ); % collapse the lens along its axis to a Fresnel structure 46 | lens1 = FresnelLens( [ 0 0 0 ], rads_outer, sags, pars, { 'air' 'pmma' } ); % Fresnel surface piece-wise identical to the surface of the original lens 47 | % back lens surface 48 | R2 = -115; 49 | lens2 = Lens( [ 0 0 0 ], D, R2, 0, { 'pmma' 'air' } ); % concave hyperbolic surface 50 | export_stl( lens1, lens2, 8, nAngles, [ flangeHeight flangeHeight ], 'Fresnel.stl' ); 51 | -------------------------------------------------------------------------------- /example13.m: -------------------------------------------------------------------------------- 1 | function example13() 2 | % 3 | % test refraction through the lens edge and backward rays refraction using the sub-aperture Maksutov-Cassegrain 4 | % telescope design 5 | % 6 | % Copyright: Yury Petrov, 2016 7 | % 8 | 9 | % create a container for optical elements (Bench class) 10 | bench = Bench; 11 | hD = 15; % diameter of the hole in the concave spherical mirror 12 | D = 30; % distance to the concave mirror 13 | d = D - 28; % distance to the convex mirror 14 | 15 | % add optical elements in the order they are encountered by light rays 16 | 17 | % back surface of the convex mirror 18 | mirror1 = Lens( [ d 0 0 ], hD, -90, -1.1, { 'mirror' 'air' } ); % pay attention to the glass order here! 19 | bench.append( mirror1 ); 20 | 21 | % spherical mirror 22 | mirror2 = Lens( [ D 0 0 ], [ hD 40 ], -89.5, 0, { 'air' 'mirror' } ); % pay attention to the glass order here! 23 | bench.append( mirror2 ); 24 | 25 | % meniscus lens on the way from concave to convex mirror 26 | lens1 = Lens( [ d + 2 0 0 ], hD, 100, 0, { 'bk7' 'air' } ); % pay attention to the glass order here! 27 | lens2 = Lens( [ d + 1 0 0 ], hD, 105, 0, { 'air' 'bk7' } ); % pay attention to the glass order here! 28 | lens2sag = surface_sag( hD, 105, 0 ); 29 | cylin = CylinderLens( [ d + 1 + lens2sag 0 0 ], hD, 1, { 'bk7' 'air' } ); % cylindrical lens equator surface 30 | bench.append( lens1 ); 31 | bench.append( cylin ); 32 | bench.append( lens2 ); 33 | 34 | % front surface of the convex mirror 35 | mirror3 = Lens( [ d 0 0 ], hD, -90, -1.1, { 'mirror' 'air' } ); % pay attention to the glass order here! 36 | bench.append( mirror3 ); 37 | 38 | % meniscus lens on the way from convex mirror to the screen 39 | % 40 | % FOR BOTH FORWARD (ALONG POSITIVE X-AXIS) AND BACKWARD (ALONG NEGATIVE X-AXIS) RAYS ORDER INTERFACE MATERIAL AS IF FOR FORWARD RAY 41 | % DIRECTION!!! HENCE, CAN REUSE THE LENS SURFACES. 42 | % 43 | bench.append( lens2 ); % pay attention to the order of surfaces here 44 | bench.append( lens1 ); 45 | 46 | % screen 47 | screen = Screen( [ 29.34 0 0 ], 0.5, 0.5, 512, 512 ); 48 | bench.append( screen ); 49 | 50 | % create collimated rays 51 | nrays = 100; 52 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 40, 'hexagonal' ); 53 | 54 | fprintf( 'Tracing rays...\n' ); 55 | rays_through = bench.trace( rays_in, 0 ); % the second parameter set to 0 enables ray tracing for rays missing some elmeents on the bench 56 | 57 | % draw bench elements and draw rays as arrows 58 | bench.draw( rays_through, 'lines', [], [ 3 0 2 1 .1 ] ); % display everything, specify arrow length for each bench element 59 | fp = rays_through( end ).focal_point; 60 | fprintf( 'The focal point of the system at: %.3f\n', fp(1) ); 61 | 62 | % get the screen image in high resolution 63 | nrays = 10000; 64 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 40, 'hexagonal' ); 65 | bench.trace( rays_in, 0 ); 66 | figure( 'Name', 'Image on the screen', 'NumberTitle', 'Off' ); 67 | imshow( screen.image, [] ); 68 | 69 | end 70 | -------------------------------------------------------------------------------- /example3.m: -------------------------------------------------------------------------------- 1 | function [ dist, ld, dv ] = example3( pupil_diameter ) 2 | % 3 | % demonstrates accommodation of the human eye by minimizing the retinal image 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | if nargin < 1 9 | pupil_diameter = 3; % mm, normal illumination 10 | end 11 | 12 | tic; 13 | disp( 'Calculating...' ); 14 | 15 | nrays = 1000; % execution time depends very little on the number of rays traced 16 | nd = 30; % number of distances tested 17 | dist = logspace( 2, 4, nd ); % tested pupil diameters, mm 18 | %dist = linspace( 70, 1000, nd ); % tested pupil diameters, mm 19 | 20 | ld = zeros( nd, 1 ); 21 | dv = zeros( nd, 1 ); 22 | 23 | % create an optical bench 24 | bench = Bench; 25 | eye = Eye( 10., pupil_diameter ); % eye with pupil diameter 10 mm 26 | bench.append( eye ); 27 | 28 | for i = 1 : nd 29 | % create some rays 30 | if i < nd % diverging rays from a point source 31 | rays_in = Rays( nrays, 'source', [ -(dist( i ) - eye.Cornea1x) 0 0 ], [ 1 0 0 ], 1/0.82 * pupil_diameter / ( dist( i ) - eye.Cornea1x ), 'hexagonal' ); 32 | else % the last point source is at infinity 33 | rays_in = Rays( nrays, 'collimated', [ -(dist( i ) - eye.Cornea1x) 0 0 ], [ 1 0 0 ], 1/0.82 * pupil_diameter, 'hexagonal' ); 34 | end 35 | 36 | % find the tightest focus 37 | if i == 1 38 | lens_diameter = 10; 39 | else 40 | lens_diameter = ld( i - 1 ); % use the previous cycle value 41 | end 42 | options = optimoptions( 'fminunc', 'Algorithm', 'quasi-newton', 'Display', 'off', 'Diagnostics', 'off' ); 43 | [ ld( i ), dv( i ) ] = fminunc( @focus, lens_diameter, options, bench, eye, rays_in ); 44 | end 45 | 46 | figure, hold on; 47 | plot( 0.1 * dist( 1 : end - 1 ), ld( 1 : end - 1 ), '-*' ); 48 | plot( 0.1 * dist( end ), ld( end ), '*' ); 49 | xlabel( 'Object distance, cm', 'FontSize', 18 ); 50 | ylabel( 'Lens diameter, mm', 'FontSize', 18 ); 51 | lbs = get( gca, 'XTickLabel' ); 52 | lbs{ end, : } = 'Inf '; 53 | set( gca, 'XTickLabel', lbs ); 54 | 55 | figure, hold on; 56 | plot( 0.1 * dist( 1 : end - 1 ), 1000 * dv( 1 : end - 1 ), '-o' ); 57 | plot( 0.1 * dist( end ), 1000 * dv( end ), 'o' ); 58 | xlabel( 'Object distance, cm', 'FontSize', 18 ); 59 | ylabel( 'Bundle focus, microns', 'FontSize', 18 ); 60 | ylim( [ 0 4 ] ); 61 | lbs = get( gca, 'XTickLabel' ); 62 | lbs{ end, : } = 'Inf '; 63 | set( gca, 'XTickLabel', lbs ); 64 | 65 | [ mdv, mi ] = min( dv ); 66 | fprintf( 'Bundle tightests focus: %.5f mm\n', mdv ); 67 | fprintf( 'Optimal lens diameter: %.3f mm\n', ld( mi ) ); 68 | fprintf( 'Optimal distance: %.3f cm\n', 0.01 * dist( mi ) ); 69 | 70 | toc; 71 | 72 | 73 | function dv = focus( ld, bench, eye, rays_in ) 74 | % trace the rays and find standard deviation of the image 75 | eye.Lens( ld ); % change eye lens parameters 76 | bench.elem{ 4 } = eye.elem{ 4 }; 77 | bench.elem{ 5 } = eye.elem{ 5 }; 78 | rays_through = bench.trace( rays_in ); 79 | [ ~, dv ] = rays_through( end ).stat; % record standard deviation of the bundle on the retina 80 | %fprintf( '%.3f\t%.5f\n', ld, dv ); 81 | -------------------------------------------------------------------------------- /example1.m: -------------------------------------------------------------------------------- 1 | function focal = example1() 2 | % 3 | % test the basic functionality of the Optometrika library 4 | % 5 | % 6 | % Copyright: Yury Petrov, 2016 7 | % 8 | 9 | % create a container for optical elements (Bench class) 10 | bench = Bench; 11 | 12 | % add optical elements in the order they are encountered by light rays 13 | 14 | % aperture 15 | aper = Aperture( [ 5 0 0 ], [ 25 80 ] ); % circular aperture 16 | bench.append( aper ); 17 | 18 | % front lens surface 19 | lens1 = Lens( [ 40 0 0 ], 58, 40, -1, { 'air' 'bk7' } ); % parabolic surface 20 | % back lens surface 21 | lens2 = Lens( [ 60 0 0 ], 58, -70, -3, { 'bk7' 'air' } ); % concave hyperbolic surface 22 | bench.append( { lens1, lens2 } ); 23 | 24 | % screen 25 | screen = Screen( [ 50.3 0 0 ], 10, 10, 512, 512 ); 26 | bench.append( screen ); 27 | 28 | %bench.rotate( [ 0 0 1 ], 0.15 ); 29 | 30 | % create some rays 31 | nrays = 500; 32 | % rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 -0.1 0 ], 58, 'hexagonal' ); 33 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 58, 'hexagonal' ); 34 | 35 | tic; 36 | 37 | npos = 50; 38 | dv = zeros( npos, 1 ); 39 | scr_x = linspace( lens2.r(1) + 30, lens2.r(1) + 60, npos ); 40 | for i = 1 : npos % loop over different screen distances 41 | screen.r(1) = scr_x( i ); % note that one can change element parameters after it was added to a Bench 42 | rays_through = bench.trace( rays_in ); % trace rays 43 | [ ~, dv( i ) ] = rays_through( end ).stat; % get stats on the last ray bundle 44 | end 45 | 46 | [ mdv, mi ] = min( dv ); 47 | focal = scr_x( mi ); 48 | fprintf( 'Back focal distance minimizing bundle std: %.3f\n', focal - lens2.r(1) ); 49 | fprintf( 'Bundle std: %.3f\n', mdv ); 50 | 51 | %find the tightest focus position 52 | f = rays_through( end - 1 ).focal_point; 53 | fprintf( 'Back focal distance based on convergence: %.3f\n', f(1) - lens2.r(1) ); 54 | 55 | % display focusing 56 | figure( 'Name', 'Optical system focusing', 'NumberTitle', 'Off' ); 57 | plot( scr_x - lens2.r(1), dv, '-*' ); 58 | xlabel( 'Screen distance from the back lens surface', 'FontSize', 16 ); 59 | ylabel( 'Bundle focus (standard deviation)', 'FontSize', 16 ); 60 | 61 | % draw rays for the tightest focus 62 | % screen.r(1) = scr_x( mi ); % set distance for which the spread was minimal 63 | screen.r(1) = f(1); % set distance for which the spread was minimal 64 | rays_through = bench.trace( rays_in ); % repeat to get the min spread rays 65 | 66 | % draw bench elements and draw rays as arrows 67 | bench.draw( rays_through, 'lines' ); % display everything, the other draw option is 'lines' 68 | scatter3( f(1), f(2), f(3), 'w*' ); % draw the convergence focal point as a white * 69 | scatter3( focal, 0, 0, 'y*' ); % draw the standard deviation focal point as a yellow * 70 | 71 | % get the screen image in high resolution for both types of focal points 72 | nrays = 10000; 73 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 58, 'hexagonal' ); 74 | bench.trace( rays_in ); 75 | figure( 'Name', 'Image on the screen, convergence focal point', 'NumberTitle', 'Off' ); 76 | imshow( screen.image, [] ); 77 | 78 | screen.r(1) = focal; 79 | bench.trace( rays_in ); 80 | figure( 'Name', 'Image on the screen, std focal point', 'NumberTitle', 'Off' ); 81 | imshow( screen.image, [] ); 82 | 83 | toc; 84 | end 85 | 86 | -------------------------------------------------------------------------------- /example7.m: -------------------------------------------------------------------------------- 1 | function example7() 2 | % 3 | % test a Fresnel lens with quadric rings 4 | % 5 | % Copyright: Yury Petrov, 2017 6 | % 7 | 8 | % create a container for optical elements (Bench class) 9 | bench = Bench; 10 | 11 | condition = 'fresnel'; % change to 'smooth' to see the original lens or to 'fresnel-sim' to simulate the original lens with a globally smooth Fresnel surface 12 | 13 | % front lens surface 14 | % This is the simulated aspheric lens surface 15 | R = 35; % lens radius of curvature 16 | k = -1; % lens aspheric constant 17 | D = 58; % lens diameter 18 | nrings = 15; % number of the Fresnel rings 19 | 20 | x1 = 30; 21 | if strcmp( condition, 'smooth' ) 22 | lens1 = Lens( [ x1 0 0 ], D, R, k, { 'air' 'pmma' } ); % smooth surface 23 | else 24 | % This simulates the above surface with a Fresnel surface 25 | rads_outer = linspace( 5, D / 2, nrings ); % outer radii, the central ring's outer radius is 5 mm 26 | rads_inner = [ 0 rads_outer( 1 : end - 1 ) ]; % the corresponding inner radii 27 | d = sqrt( 1 - ( 1 + k ) * ( rads_inner / R ).^2 ); 28 | angs = pi/2 - atan( rads_inner ./ ( R * d ) ); % cone half-angle at the inner ring radius with respect to the x-axis direction 29 | angs(1) = k; % replace the angle at the center (normally, pi/2) with the central part's conic constant k 30 | 31 | % % Fresnel surface made of cone segments 32 | % pars = angs; 33 | 34 | % Fresnel surface made of quadric segments 35 | pars = [ angs; ... 36 | repmat( pi - pi/8, 1, length( angs ) ); ... % wall half-angle at the outer ring radius, here less vertical by pi/8 (optional, defaults to pi) 37 | R ./ rads_outer ]; % ring's radial radius of curvature in units of the ring's outer radius (optional, defaults to Inf, i.e. to a flat conical surface) 38 | if strcmp( condition, 'fresnel' ) 39 | sags = zeros( length( rads_inner ), 1 ); % collapse the lens along its axis to a Fresnel structure 40 | x1 = 42; 41 | lens1 = FresnelLens( [ x1 0 0 ], rads_outer, sags, pars, { 'air' 'pmma' } ); % Fresnel surface piece-wise identical to the surface of the original lens 42 | else 43 | if k == -1 44 | sags = rads_inner.^2 ./ ( 2 * R ); 45 | else 46 | sags = R / ( 1 + k ) * ( 1 - d ); % keep the original lens's profile 47 | end 48 | lens1 = FresnelLens( [ x1 0 0 ], rads_outer, sags, pars, { 'air' 'pmma' } ); % Fresnel surface globally identical to the surface of the original lens 49 | end 50 | end 51 | bench.append( lens1 ); 52 | 53 | % back lens surface 54 | R2 = -115; 55 | x2 = 48; 56 | lens2 = Lens( [ x2 0 0 ], D, R2, 0, { 'pmma' 'air' } ); % concave hyperbolic surface 57 | bench.append( lens2 ); 58 | 59 | % screen 60 | screen = Screen( [ 94.5 0 0 ], 5, 5, 512, 512 ); 61 | bench.append( screen ); 62 | 63 | tic; 64 | fprintf( 'Tracing rays... ' ); 65 | nrays = 100; 66 | rays_in = Rays( nrays, 'collimated', [ 0 0 0 ], [ 1 0 0 ], 50, 'hexagonal' ); 67 | rays_through = bench.trace( rays_in ); 68 | toc; 69 | 70 | % draw bench elements and draw rays as arrows 71 | bench.draw( rays_through, 'rays' ); % display everything, the other draw option is 'lines' 72 | figure, imshow( screen.image, [ 0 max( max( screen.image ) ) ] ); 73 | 74 | % draw the lens for manufacturing 75 | [ ~, pr ] = draw_lens_engineering( [ 0, x2 - x1 ], [ D D ], [ R R2 ], [ k 0 ], [], 'pmma', [], 'AR', 'Fresnel', lens1 ); 76 | fprintf( 'Engineering drawing saved\n' ); 77 | save lens_profile.mat pr; 78 | 79 | 80 | -------------------------------------------------------------------------------- /conic_intersection.m: -------------------------------------------------------------------------------- 1 | function rinter = conic_intersection( r_in, e, surf ) 2 | % 3 | % returns intersection vector with ellipsoid, paraboloid, or hyperboloid 4 | % surface 5 | % 6 | 7 | x0 = r_in( :, 1 ); 8 | y0 = r_in( :, 2 ); 9 | z0 = r_in( :, 3 ); 10 | e1 = e( :, 1 ); 11 | e2 = e( :, 2 ); 12 | e3 = e( :, 3 ); 13 | k = surf.k; 14 | a = 1 + k; 15 | R = surf.R(1); 16 | 17 | if a == 0 % paraboloid, special case 18 | A = e2.^2 + e3.^2; 19 | B = e1 * R - e2 .* y0 - e3 .* z0; 20 | D = B.^2 - A .* ( -2 * R * x0 + y0.^2 + z0.^2 ); 21 | D( D < 0 ) = Inf; % mark no intersection as Inf, I is nulled below anyway 22 | 23 | d01 = ( y0.^2 + z0.^2 ) / ( 2 * R ) - x0; % distance to the intersection for the ray || to the paraboloid 24 | d02 = d01; 25 | else 26 | A = e1 .* ( a * e1.^2 + e2.^2 + e3.^2 ); 27 | B = e1.^2 * R - a * e1.^2 .* x0 - e1 .* e2 .* y0 - e1 .* e3 .* z0; 28 | D = e1.^2 .* ( e3.^2 .* ( 2 * R * x0 - a * x0.^2 - y0.^2 ) + 2 * e2 .* e3 .* y0 .* z0 - ... 29 | 2 * e1 .* ( R - a * x0 ) .* ( e2 .* y0 + e3 .* z0 ) + e2.^2 .* ( 2 * R * x0 - a * x0.^2 - z0.^2 ) + ... 30 | e1.^2 .* ( R^2 - a * ( y0.^2 + z0.^2 ) ) ); 31 | D( D < 0 ) = Inf; % mark no intersection as Inf, I is nulled below anyway 32 | 33 | A0 = 2 * ( 2 * a * e2 .* e3 .* y0 .* z0 + e2.^2 .* ( R^2 - a * z0.^2 ) + e3.^2 .* ( R^2 - a * y0.^2 ) ); 34 | B0 = a * ( 1 - a ) * ( a * ( e1 .* x0 - e2 .* y0 - e3 .* z0 ) - R * e1 ) .* ( y0.^2 + z0.^2 ); 35 | d01 = B0 ./ A0; % distance to the intersection for the ray || to the hyperboloid sides 36 | d02 = -B0 ./ A0; 37 | end 38 | d1 = ( B + sqrt( D ) ) ./ A; 39 | d2 = ( B - sqrt( D ) ) ./ A; 40 | 41 | % it is necessary to eliminate infinities before the following logical operation 42 | d1( ~isfinite( d1 ) ) = 0; 43 | d2( ~isfinite( d2 ) ) = 0; 44 | d01( ~isfinite( d01 ) ) = 0; 45 | d02( ~isfinite( d02 ) ) = 0; 46 | 47 | d( :, 1 ) = ( abs( A ) <= eps ) .* d01 + ... % ray parallel to the paraboloid / hyperboloid, special case 48 | ( abs( A ) > eps ) .* d1; 49 | d( :, 2 ) = ( abs( A ) <= eps ) .* d02 + ... % ray parallel to the paraboloid / hyperboloid, special case 50 | ( abs( A ) > eps ) .* d2; 51 | 52 | if a * R < 0 % hyperboloid with positive R, or ellipsoid with negative R: we want to consider only the top branch 53 | ind = x0 - R / a * ( 1 + sqrt( 1 - a * ( y0.^2 + z0.^2 ) / R^2 ) ) < 0 & e1 >= 0 | ... % forward sources below the bottom branch 54 | x0 - R / a * ( 1 - sqrt( 1 - a * ( y0.^2 + z0.^2 ) / R^2 ) ) < 0 & e1 < 0; % or backward sources below the top branch 55 | d( ind, 1 ) = NaN; %realmax; % disregard intersections with the lower branch 56 | elseif a * R > 0 % hyperboloid with negative R, or ellipsoid with positive R: we want to consider only the bottom branch 57 | ind = x0 - R / a * ( 1 - sqrt( 1 - a * ( y0.^2 + z0.^2 ) / R^2 ) ) > 0 & e1 >= 0 | ... % forward sources above the bottom branch 58 | x0 - R / a * ( 1 + sqrt( 1 - a * ( y0.^2 + z0.^2 ) / R^2 ) ) > 0 & e1 < 0; % or backward sources above the top branch 59 | d( ind, 2 ) = NaN; %realmax; % disregard intersections with the top branch 60 | end 61 | d( d <= 1e-12 ) = NaN; %realmax; % intensities for these rays (non-intersecting the surface) will be set to 0 anyway 62 | 63 | [ ~, ii ] = min( d, [], 2 ); 64 | ind = sub2ind( size( d ), ( 1:size( d, 1 ) )', ii ); % linear index of the min value 65 | d = abs( d( ind ) ); 66 | 67 | % form the intersection vector 68 | rinter = r_in + repmat( d, 1, 3 ) .* e; 69 | 70 | -------------------------------------------------------------------------------- /CylinderLens.m: -------------------------------------------------------------------------------- 1 | classdef CylinderLens < Surface 2 | % CYLINDERLENS Implements a cylindrical lens surface. 3 | % 4 | % Member functions: 5 | % 6 | % l = CylinderLens( r, D, h, glass ) - object constructor 7 | % INPUT: 8 | % r - 1x3 position vector 9 | % D - cylinder diameter, [ Dy Dz ] vector for an elliptical cylinder 10 | % h - cylinder height 11 | % glass - 1 x 2 cell array of strings, e.g., { 'air' 'acrylic' } 12 | % OUTPUT: 13 | % l - lens surface object 14 | % 15 | % l.display() - displays the surface l information 16 | % 17 | % l.draw() - draws the surface l in the current axes 18 | % 19 | % l.rotate( rot_axis, rot_angle ) - rotate the surface l 20 | % INPUT: 21 | % rot_axis - 1x3 vector defining the rotation axis 22 | % rot_angle - rotation angle (radians) 23 | % 24 | % Copyright: Yury Petrov, 2016 25 | % 26 | 27 | properties 28 | D = 1; % cylinder diameter, [ Dy Dz ] 29 | h = 1; % cylinder height 30 | end 31 | 32 | methods 33 | function self = CylinderLens( ar, aD, ah, aglass ) 34 | if nargin == 0 35 | return; 36 | end 37 | self.D = aD; 38 | self.r = ar; 39 | self.h = ah; 40 | self.glass = aglass; 41 | self.R = aD / 2; % this is needed for the correct handling of elliptic cylinders 42 | end 43 | 44 | function display( self ) 45 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 46 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 47 | if length( self.D ) == 1 48 | fprintf( 'Diameter:\t %.3f\n', self.D ); 49 | else 50 | fprintf( 'Y Diameter:\t %.3f\n', self.D(1) ); 51 | fprintf( 'Z Diameter:\t %.3f\n', self.D(2) ); 52 | end 53 | fprintf( 'Height:\t %.3f\n', self.h ); 54 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 55 | end 56 | 57 | function h = draw( self, color ) 58 | % DISPLAY the cylindrical lens surface 59 | if nargin < 2 60 | color = [ 1 1 1 .5 ]; 61 | end 62 | nang = 100; 63 | [ x, y, z ] = cylinder( self.D(1) / 2, nang ); 64 | z( 2, : ) = self.h; 65 | S = [ z(:) -y(:) x(:) ]; % put the cone into the Optometrika reference frame 66 | if length( self.D ) > 1 % elliptical cylinder 67 | S( :, 3 ) = S( :, 3 ) * self.D(2) / self.D(1); 68 | end 69 | 70 | % rotate and shift 71 | if self.rotang ~= 0 72 | S = rodrigues_rot( S, self.rotax, self.rotang ); 73 | end 74 | x(:) = S( :, 1 ) + self.r( 1 ); 75 | y(:) = S( :, 2 ) + self.r( 2 ); 76 | z(:) = S( :, 3 ) + self.r( 3 ); 77 | 78 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 79 | h = surf( x, y, z, c, ... 80 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 81 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 82 | end 83 | 84 | function rotate( self, rot_axis, rot_angle ) 85 | self.rotate@Surface( rot_axis, rot_angle ); % rotate the surface members 86 | end 87 | 88 | end 89 | 90 | end 91 | 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Optometrika 2 | 3 | ![optometrika](https://user-images.githubusercontent.com/46988982/51661552-0c009200-1f66-11e9-8d38-79f35f6ac8d8.png) 4 | 5 | ## Overview 6 | 7 | Optometrika MATLAB library implements analytical and iterative ray tracing approximation to optical image formation using Snell’s and Fresnel’s laws of refraction and reflection. 8 | 9 | Currently, the library implements refractive and reflective general surfaces, aspheric (conic) surfaces with astigmatism, Fresnel surfaces, cones and cylinders (elliptic too), planes, circular and ring-shaped apertures, rectangular flat screens, spheroidal screens, and a realistic model of the human eye with accommodating lens and spheroidal retina. See example*.m files for examples of ray tracing in general (user-defined shape) lenses, aspheric lenses, Fresnel lenses, prisms, mirrors, and human eye. 10 | 11 | The library traces refracted rays, including intensity loss at the refractive surface. Reflected rays are currently traced for mirrors and also for a single total internal reflexion or double refraction, if it happens. Note that the Bench class object is not a real physical bench, it is only an ordered array of optical elements, and it is your responsibility to arrange optical objects in the right order. In particular, if you need to trace rays passing through the same object multiple times, you have to add the object multiple times to the bench array in the order the object is encountered by the rays. For example, double refraction/reflection for cylindrical and conical surfaces can be calculated by adding the surface twice to the bench (see example10). 12 | 13 | The library is compact and fast. It was written using Matlab classes and is fully vectorized. It takes about 2 seconds to trace 100,000 rays through an external lens and the human eye (8 optical surfaces) on a 3 GHz Intel Core i7 desktop. Fresnel lens tracing is somewhat slower due to looping through the Fresnel cones describing the lens surface. Tracing through user-defined (general) surfaces is significantly slower due to iterative search of ray intersections with the surface. 14 | 15 | Thank you for downloading Optometrika, enjoy it! 16 | 17 | ## List of examples: 18 | 19 | example1.m: tests the basic functionality of the Optometrika library 20 | 21 | example2.m: demonstrates the Optometrika's optical model of the human eye 22 | 23 | example3.m: demonstrates accommodation of the human eye by minimizing the retinal image 24 | 25 | example4.m: tests a ring lens with the cosine surface profile defined in coslens.m 26 | 27 | example5.m: tests planar mirrors 28 | 29 | example6.m: tests planar and parabolic mirrors (a Newtonian refractor telescope) 30 | 31 | example7.m: tests a Fresnel lens 32 | 33 | example8.m: tests a lens with polynomial aspheric terms 34 | 35 | example9.m: tests cone mirrors 36 | 37 | example10.m: tests cylinder and cone surfaces with double refraction 38 | 39 | example11.m: demonstrates ray tracing for rays originating inside the human eye 40 | 41 | example12.m: draws a lens and determines its front surface, back surface, and total height. Makes an animated gif of the lens and an engineering drawing of the lens. 42 | 43 | example13.m: tests refraction through the lens edge and backward rays refraction (sub-aperture Maksutov-Cassegrain telescope) 44 | 45 | example14.m: tests refraction through a lens with astigmatism (different vertical and horizontal radii of curvature) 46 | 47 | example15.m: simulates a hexagonal array of spherical micro lenses 48 | 49 | example16.m: demonstrates STL export of various lenses 50 | -------------------------------------------------------------------------------- /ConeLens_old.m: -------------------------------------------------------------------------------- 1 | classdef ConeLens < Surface 2 | % CONELENS Implements a cone lens surface. 3 | % 4 | % Member functions: 5 | % 6 | % l = ConeLens( r, D, the, glass ) - object constructor 7 | % INPUT: 8 | % r - 1x3 position vector 9 | % D - 2 x 1 (inner outer diameters) or 1 x 1 vector (outer diameter) 10 | % the - cone half-angle, radians 11 | % glass - 1 x 2 cell array of strings, e.g., { 'air' 'acrylic' } 12 | % OUTPUT: 13 | % l - lens surface object 14 | % 15 | % l.display() - displays the surface l information 16 | % 17 | % l.draw() - draws the surface l in the current axes 18 | % 19 | % l.rotate( rot_axis, rot_angle ) - rotate the surface l 20 | % INPUT: 21 | % rot_axis - 1x3 vector defining the rotation axis 22 | % rot_angle - rotation angle (radians) 23 | % 24 | % Copyright: Yury Petrov, 2016 25 | % 26 | 27 | properties 28 | D = [ 0; 1 ]; % lens diameter: inner/outer 29 | rad = [ 0; 1 ] % cone radii 30 | the = pi/4; % cone half-angle 31 | end 32 | 33 | methods 34 | function self = ConeLens( ar, aD, ath, aglass ) 35 | if nargin == 0 36 | return; 37 | end 38 | if size( aD, 1 ) < size( aD, 2 ) 39 | aD = aD'; 40 | end 41 | if size( aD, 1 ) == 1 42 | aD = [ 0; aD ]; % assume inner radius = 0 43 | end 44 | self.D = aD; 45 | self.r = ar; 46 | self.rad = aD / 2; 47 | self.the = ath; 48 | self.glass = aglass; 49 | end 50 | 51 | function display( self ) 52 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 53 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 54 | fprintf( 'Diameter:\t %.3f\n', self.D(2) ); 55 | if self.D(1) ~= 0 56 | fprintf( 'Inner diameter:\t %.3f\n', self.D(1) ); 57 | end 58 | fprintf( 'Slope (rad):\t %.3f\n', abs( self.the ) ); 59 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 60 | end 61 | 62 | function h = draw( self, color ) 63 | % DISPLAY the cone lens surface 64 | if nargin < 2 65 | color = [ 1 1 1 .5 ]; 66 | end 67 | nang = 100; 68 | [ x, y, z ] = cylinder( self.rad, nang ); 69 | z( 2, : ) = ( self.rad( 2 ) - self.rad( 1 ) ) / tan( self.the ); 70 | S = [ z(:) -y(:) x(:) ]; % put the cone into the Optometrika reference frame 71 | 72 | % rotate and shift 73 | if self.rotang ~= 0 74 | S = rodrigues_rot( S, self.rotax, self.rotang ); 75 | end 76 | x(:) = S( :, 1 ) + self.r( 1 ); 77 | y(:) = S( :, 2 ) + self.r( 2 ); 78 | z(:) = S( :, 3 ) + self.r( 3 ); 79 | 80 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 81 | h = surf( x, y, z, c, ... 82 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 83 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 84 | end 85 | 86 | function rotate( self, rot_axis, rot_angle ) 87 | self.rotate@Surface( rot_axis, rot_angle ); % rotate the surface members 88 | if abs( rot_angle ) > pi/2 89 | self.the = pi - self.the; 90 | end 91 | end 92 | 93 | end 94 | 95 | end 96 | 97 | -------------------------------------------------------------------------------- /GeneralLens.m: -------------------------------------------------------------------------------- 1 | classdef GeneralLens < Surface 2 | % GENERALLENS Implements a lens surface of an arbitrary shape. This 3 | % class requires iterative search of intersections with a ray, 4 | % therefore it works slower than Lens. 5 | % 6 | % Member functions: 7 | % 8 | % l = GeneralLens( r, D, func, glass, varargin ) - object constructor 9 | % INPUT: 10 | % r - 1x3 position vector 11 | % D - diameter 12 | % func - function name string 13 | % glass - 1 x 2 cell array of strings, e.g., { 'air' 'acrylic' } 14 | % varargin - an arbitrary number of parameters required by func. 15 | % OUTPUT: 16 | % l - lens surface object 17 | % 18 | % display() - displays the object's information 19 | % 20 | % draw() - draws the object in the current axes 21 | % 22 | % rotate( rot_axis, rot_angle ) - rotate the surface by rot_angle 23 | % (radians) about the 1x3 rotation axis. 24 | % 25 | % Copyright: Yury Petrov, 2016 26 | % 27 | 28 | properties 29 | D = [ 0; 1 ] % lens diameter (inner, outer) 30 | funcs = '' % lens surface function name string 31 | funch = [] % the corresponding function handle 32 | funca = [] % argument list for the function 33 | end 34 | 35 | methods 36 | function self = GeneralLens( ar, aD, afunc, aglass, varargin ) 37 | if nargin == 0 38 | return; 39 | end 40 | if size( aD, 1 ) < size( aD, 2 ) 41 | aD = aD'; 42 | end 43 | if size( aD, 1 ) == 1 44 | aD = [ 0; aD ]; % assume inner radius = 0 45 | end 46 | self.r = ar; 47 | self.D = aD; 48 | self.funcs = afunc; 49 | self.funch = str2func( afunc ); % construct function handle from the function name string 50 | self.glass = aglass; 51 | self.funca = varargin; 52 | end 53 | 54 | function display( self ) 55 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 56 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 57 | fprintf( 'Diameter:\t %.3f\n', self.D(2) ); 58 | if self.D(1) ~= 0 59 | fprintf( 'Inner diameter:\t %.3f\n', self.D(1) ); 60 | end 61 | fprintf( 'Surface function:\t %s\n', self.func ); 62 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 63 | end 64 | 65 | function h = draw( self, color ) 66 | % DISPLAY the lens surface 67 | if nargin < 2 68 | color = [ 1 1 1 .5 ]; 69 | end 70 | nrad = 50; 71 | rad = linspace( self.D(1) / 2, self.D(2) / 2, nrad ); 72 | nang = 100; 73 | ang = linspace( 0, 2 * pi, nang ); 74 | [ ang, rad ] = meshgrid( ang, rad ); 75 | 76 | [ y, z ] = pol2cart( ang, rad ); 77 | x = self.funch( y, z, self.funca, 0 ); 78 | S = [ x(:) y(:) z(:) ]; 79 | 80 | % rotate and shift 81 | if self.rotang ~= 0 82 | S = rodrigues_rot( S, self.rotax, self.rotang ); 83 | end 84 | x(:) = S( :, 1 ) + self.r( 1 ); 85 | y(:) = S( :, 2 ) + self.r( 2 ); 86 | z(:) = S( :, 3 ) + self.r( 3 ); 87 | 88 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 89 | h = surf( x, y, z, c, ... 90 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 91 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 92 | end 93 | 94 | end 95 | 96 | end 97 | 98 | -------------------------------------------------------------------------------- /AsphericLens.m: -------------------------------------------------------------------------------- 1 | classdef AsphericLens < GeneralLens 2 | % ASPHERICLENS Implements a lens surface given by a rotation of a conic curve 3 | % with additional polynomial terms, its sag given by 4 | % s = r^2 / R / ( 1 + sqrt( 1 - ( k + 1 ) * r^2 / R^2 ) ) + a(1) * r^2 + 5 | % a(2) * r^4 + ... + a(n) * r^2n, where 6 | % R is the tangent sphere radius, k is the aspheric factor: 7 | % 0 < k - oblate spheroid 8 | % k = 0 - sphere 9 | % -1 < k < 0 - prolate spheroid 10 | % k = -1 - parabola 11 | % k < -1 - hyperbola 12 | % and a(1) ... a(n) are the polynomial aspheric terms 13 | % 14 | % Member functions: 15 | % 16 | % l = AsphericLens( r, D, R, k, avec, glass ) - object constructor 17 | % INPUT: 18 | % r - 1x3 position vector 19 | % D - diameter 20 | % R - tangent sphere radius, [ Ry Rz ] vector for an astigmatic surface 21 | % k - conic coefficient, for astigmatic surface corresponds to the y-axis 22 | % avec - a vector of the polynomial terms 23 | % glass - 1 x 2 cell array of strings, e.g., { 'air' 'acrylic' } 24 | % OUTPUT: 25 | % l - lens surface object 26 | % 27 | % l.display() - displays the surface l information 28 | % 29 | % l.draw() - draws the surface l in the current axes 30 | % 31 | % l.rotate( rot_axis, rot_angle ) - rotate the surface l 32 | % INPUT: 33 | % rot_axis - 1x3 vector defining the rotation axis 34 | % rot_angle - rotation angle (radians) 35 | % 36 | % Copyright: Yury Petrov, 2016 37 | % 38 | 39 | properties 40 | avec = []; % vector of polynomial coeffeicient 41 | end 42 | 43 | methods 44 | function self = AsphericLens( ar, aD, aR, ak, aavec, aglass ) 45 | if nargin == 0 46 | return; 47 | end 48 | if size( aD, 1 ) < size( aD, 2 ) 49 | aD = aD'; 50 | end 51 | if size( aD, 1 ) == 1 52 | aD = [ 0; aD ]; 53 | end 54 | self.r = ar; 55 | self.D = aD; 56 | self.R = aR; 57 | self.k = ak; 58 | if size( aavec, 1 ) > size( aavec, 2 ) 59 | aavec = aavec'; 60 | end 61 | self.avec = aavec; 62 | self.glass = aglass; 63 | self.funcs = 'asphlens'; 64 | self.funch = str2func( self.funcs ); % construct function handle from the function name string 65 | if length( self.R ) == 1 % no astigmatism 66 | self.funca = [ self.R, self.R, self.k, self.avec ]; 67 | else 68 | self.funca = [ self.R, self.k, self.avec ]; 69 | end 70 | end 71 | 72 | function display( self ) 73 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 74 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 75 | fprintf( 'Diameter:\t %.3f\n', self.D ); 76 | fprintf( 'Curv. radius:\t %.3f\n', self.R ); 77 | fprintf( 'Asphericity:\t %.3f\n', self.k ); 78 | fprintf( 'Polynomial coefficients:' ); 79 | disp( self.avec ); 80 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 81 | end 82 | 83 | function h = draw( self, color ) 84 | % DISPLAY the lens surface 85 | if nargin < 2 86 | color = [ 1 1 1 .5 ]; 87 | end 88 | h = self.draw@GeneralLens( color ); 89 | end 90 | 91 | function rotate( self, rot_axis, rot_angle ) 92 | self.rotate@Surface( rot_axis, rot_angle ); % rotate the surface members 93 | if abs( rot_angle ) > pi/2 94 | self.avec = -self.avec; 95 | self.funca = [ self.R, self.k, self.avec ]; 96 | end 97 | end 98 | end 99 | 100 | end 101 | 102 | -------------------------------------------------------------------------------- /example2.m: -------------------------------------------------------------------------------- 1 | function example2() 2 | % 3 | % demonstrates the Optometrika's optical model of the human eye 4 | % 5 | % Copyright: Yury Petrov, 2016 6 | % 7 | 8 | % demonstrate resolution dependence on the pupil diameter 9 | tic; 10 | disp( 'Calculating...' ); 11 | 12 | % add some optical elements 13 | nrays = 1000; 14 | nd = 20; % number of pupil diameters tested 15 | pd = linspace( 2, 8, nd ); % tested pupil diameters, mm 16 | 17 | dv = zeros( nd, 1 ); 18 | for i = 1 : nd 19 | % create an optical bench 20 | bench = Bench; 21 | pupil_diameter = pd( i ); 22 | % eye = Eye( [], pupil_diameter ); % eye accommodating at infinity 23 | eye = Eye( 9.4, pupil_diameter ); % eye accommodating at infinity 24 | %eye.rotate( [ 0 0 1 ], 1=30 * pi / 180 ); 25 | bench.append( eye ); 26 | 27 | % create some rays 28 | rays_in = Rays( nrays, 'collimated', [ -20 0 0 ], [ 1 0 0 ], 1.2 * pupil_diameter, 'hexagonal' ); 29 | 30 | % trace the rays 31 | rays_through = bench.trace( rays_in ); 32 | [ ~, dv( i ) ] = rays_through( end ).stat; % record standard deviation of the bundle on the retina 33 | end 34 | 35 | [ mdv, mi ] = min( dv ); 36 | fprintf( 'Bundle tightests focus: %.5f mm\n', mdv ); 37 | fprintf( 'Optimal pupil diameter: %.5f mm\n', pd( mi ) ); 38 | 39 | toc; 40 | tic; 41 | disp( 'Drawing...' ); 42 | 43 | figure( 'Name', 'Eye focusing vs. Pupil diameter', 'NumberTitle', 'Off' ); 44 | plot( pd, dv, '*-' ); 45 | xlabel( 'Pupil diameter, mm', 'FontSize', 16 ); 46 | ylabel( 'Bundle focus (standard deviation), mm', 'FontSize', 16 ); 47 | 48 | % draw the result for the optimal pupil diameter 49 | bench = Bench; 50 | eye = Eye( [], pd( mi ) ); 51 | % eye.rotate( [ 0 0 1 ], 20 * pi / 180 ); 52 | bench.append( eye ); 53 | nrays = 40; 54 | rays_in = Rays( nrays, 'collimated', [ -20 0 0 ], [ 1 0 0 ], 1.2 * pd( mi ), 'hexagonal' ); 55 | rays_in.color( ceil( rays_in.cnt / 2 ), : ) = [ 1 0 0 ]; % draw the central ray in red 56 | rays_through = bench.trace( rays_in ); 57 | bench.draw( rays_through, 'lines' ); 58 | 59 | toc; 60 | 61 | % accommodation (lens diameter) from infinity to 7D (14 cm) 62 | tic; 63 | disp( 'Calculating...' ); 64 | 65 | % add some optical elements 66 | pupil_diameter = 3; % normal light level 67 | nrays = 1000; 68 | nd = 30; % number of lens diameters tested 69 | ld = linspace( 10.610, 9.5, nd ); % tested pupil diameters, mm 70 | viewing_distance = 140; % mm 71 | 72 | % create some rays 73 | rays_in = Rays( nrays, 'source', [ -(viewing_distance + 13.3) 0 0 ], [ 1 0 0 ], 1.2 * pupil_diameter / (viewing_distance + 13.3), 'hexagonal' ); 74 | 75 | dv = zeros( nd, 1 ); 76 | for i = 1 : nd 77 | % create an optical bench 78 | bench = Bench; 79 | lens_diameter = ld( i ); 80 | eye = Eye( lens_diameter, pupil_diameter ); % eye accommodating at infinity 81 | %eye.eye_lens_vol( lens_diameter ) 82 | bench.append( eye ); 83 | % trace the rays 84 | rays_through = bench.trace( rays_in ); 85 | [ ~, dv( i ) ] = rays_through( end ).stat; % record standard deviation of the bundle on the retina 86 | end 87 | 88 | [ mdv, mi ] = min( dv ); 89 | fprintf( 'Bundle tightests focus: %.5f mm\n', mdv ); 90 | fprintf( 'Optimal lens diameter: %.5f mm\n', ld( mi ) ); 91 | 92 | toc; 93 | tic; 94 | disp( 'Drawing...' ); 95 | 96 | figure( 'Name', 'Eye focusing vs. Lens diameter', 'NumberTitle', 'Off' ); 97 | plot( ld, dv, '-o' ); 98 | xlabel( 'Lens diameter, mm', 'FontSize', 16 ); 99 | ylabel( 'Bundle focus (standard deviation), mm', 'FontSize', 16 ); 100 | set( gca, 'XDir', 'reverse' ); 101 | 102 | % draw the result for the optimal pupil diameter 103 | bench = Bench; 104 | eye = Eye( ld( mi ), pupil_diameter ); 105 | bench.append( eye ); 106 | nrays = 40; 107 | rays_in = Rays( nrays, 'source', [ -(viewing_distance + 13.3) 0 0 ], [ 1 0 0 ], 1.2 * pupil_diameter / (viewing_distance + 13.3), 'hexagonal' ); 108 | rays_through = bench.trace( rays_in ); 109 | bench.draw( rays_through, 'lines' ); 110 | 111 | toc; 112 | 113 | end 114 | 115 | -------------------------------------------------------------------------------- /ConeLens.m: -------------------------------------------------------------------------------- 1 | classdef ConeLens < Surface 2 | % CONELENS Implements a cone lens surface. 3 | % 4 | % Member functions: 5 | % 6 | % l = ConeLens( r, D, h, the, glass ) - object constructor 7 | % INPUT: 8 | % r - 1x3 position vector 9 | % D - cone top (smaller x coordinate) diameter, [ Dy Dz ] vector for an elliptical cone 10 | % h - cone height 11 | % the - cone half-angle, radians 12 | % glass - 1 x 2 cell array of strings, e.g., { 'air' 'acrylic' } 13 | % OUTPUT: 14 | % l - lens surface object 15 | % 16 | % l.display() - displays the surface l information 17 | % 18 | % l.draw() - draws the surface l in the current axes 19 | % 20 | % l.rotate( rot_axis, rot_angle ) - rotate the surface l 21 | % INPUT: 22 | % rot_axis - 1x3 vector defining the rotation axis 23 | % rot_angle - rotation angle (radians) 24 | % 25 | % Copyright: Yury Petrov, 2016 26 | % 27 | 28 | properties 29 | D = 0; % cone top diameter 30 | h = 1; % cone height 31 | rad = [ 0; 1 ] % cone top/bottom radii 32 | the = pi/4; % cone half-angle 33 | end 34 | 35 | methods 36 | function self = ConeLens( ar, aD, ah, ath, aglass ) 37 | if nargin == 0 38 | return; 39 | end 40 | self.r = ar; 41 | self.D = aD; 42 | self.h = ah; 43 | self.the = ath; 44 | self.glass = aglass; 45 | self.rad(1) = aD(1) / 2; % top radius along y-axis 46 | self.rad(2) = self.rad(1) + ah * tan( ath ); % bottom radius along y-axis 47 | self.R = aD / 2; % this is needed for the correct handling of elliptic cones 48 | end 49 | 50 | function display( self ) 51 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 52 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 53 | if length( self.D ) == 1 54 | fprintf( 'Diameter:\t %.3f\n', self.D ); 55 | else % elliptic cylinder 56 | fprintf( 'Y Diameter:\t %.3f\n', self.D(1) ); 57 | fprintf( 'Z Diameter:\t %.3f\n', self.D(2) ); 58 | end 59 | fprintf( 'Height:\t %.3f\n', self.h ); 60 | fprintf( 'Slope (rad):\t %.3f\n', abs( self.the ) ); 61 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 62 | end 63 | 64 | function h = draw( self, color ) 65 | % DISPLAY the cone lens surface 66 | if nargin < 2 67 | color = [ 1 1 1 .5 ]; 68 | end 69 | nang = 100; 70 | [ x, y, z ] = cylinder( self.rad, nang ); 71 | z( 2, : ) = self.h; 72 | S = [ z(:) -y(:) x(:) ]; % put the cone into the Optometrika reference frame 73 | if length( self.D ) > 1 % elliptical cone 74 | S( :, 3 ) = S( :, 3 ) * self.D(2) / self.D(1); 75 | end 76 | 77 | % rotate and shift 78 | if self.rotang ~= 0 79 | S = rodrigues_rot( S, self.rotax, self.rotang ); 80 | end 81 | x(:) = S( :, 1 ) + self.r( 1 ); 82 | y(:) = S( :, 2 ) + self.r( 2 ); 83 | z(:) = S( :, 3 ) + self.r( 3 ); 84 | 85 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 86 | h = surf( x, y, z, c, ... 87 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 88 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 89 | end 90 | 91 | function rotate( self, rot_axis, rot_angle ) 92 | self.rotate@Surface( rot_axis, rot_angle ); % rotate the surface members 93 | if abs( rot_angle ) > pi/2 94 | self.the = pi - self.the; 95 | end 96 | end 97 | 98 | end 99 | 100 | end 101 | 102 | -------------------------------------------------------------------------------- /Retina.m: -------------------------------------------------------------------------------- 1 | classdef Retina < Surface 2 | % RETINA implements a spherical/ellipsoidal screen 3 | % 4 | % Member functions: 5 | % 6 | % p = Retina( r, R, k ) - object constructor 7 | % INPUT: 8 | % r - 1x3 position vector 9 | % R - tangent sphere radius 10 | % k - conic coefficient for the surface 11 | % OUTPUT: 12 | % p - retina object 13 | % 14 | % p.display() - displays the retina p information 15 | % 16 | % p.draw() - draws the retina p in the current axes 17 | % 18 | % p.rotate( rot_axis, rot_angle ) - rotate the retina 19 | % INPUT: 20 | % rot_axis - 1x3 vector defining the rotation axis 21 | % rot_angle - rotation angle (radians) 22 | % 23 | % Copyright: Yury Petrov, 2016 24 | % 25 | 26 | properties 27 | D = 1; % retina diameter 28 | ang = pi/2; % aperture angle 29 | azbins = 512; % number of azimuth bins 30 | elbins = 512; % number of elevation bins 31 | image = []; 32 | end 33 | 34 | properties ( SetAccess = private ) 35 | end 36 | 37 | methods 38 | function self = Retina( ar, aR, ak, aang, aazbins, aelbins ) 39 | if nargin == 0 40 | return; 41 | end 42 | if nargin < 6 43 | aelbins = 512; 44 | end 45 | if nargin < 5 46 | aazbins = 512; 47 | end 48 | if nargin < 4 49 | aang = 0.69 * pi/2; % to fit the lens 50 | end 51 | if nargin < 3 52 | ak = 0; 53 | end 54 | if nargin < 2 55 | error( 'At least position and radius Retina parameters must be specified!' ); 56 | end 57 | self.r = ar; 58 | self.R = aR; 59 | self.k = ak; 60 | self.ang = aang; 61 | self.azbins = aazbins; 62 | self.elbins = aelbins; 63 | if ak <= -1 % not a sphere or ellipsoid 64 | error( 'Aspheric parameter has to be larger than -1 for Retina' ); 65 | end 66 | self.D = 2 * abs( self.R ) ./ sqrt( 1 + self.k ); 67 | end 68 | 69 | function display( self ) 70 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 71 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 72 | fprintf( 'Diameter:\t %.3f\n', self.D ); 73 | fprintf( 'Curv. radius:\t %.3f\n', self.R ); 74 | fprintf( 'Asphericity:\t %.3f\n', self.k ); 75 | end 76 | 77 | function h = draw( self, color ) 78 | % DISPLAY the spherical surface 79 | if nargin < 2 80 | color = [ 0.2 0.2 0.2 1 ]; % dull gray opaque color 81 | end 82 | theta = linspace( -pi, pi, self.azbins ); % azimuth 83 | phi = linspace( -pi/2, self.ang, self.elbins )'; % elevation 84 | cosphi = cos( phi ); cosphi(1) = 0; 85 | sintheta = sin( theta ); sintheta(1) = 0; sintheta( end ) = 0; 86 | y = cosphi * cos( theta ); 87 | z = cosphi * sintheta; 88 | x = sin( phi ) * ones( 1, length( theta ) ); 89 | S = self.R * [ ( x(:) + 1 ) / ( 1 + self.k ), y(:) / sqrt( 1 + self.k ), z(:) / sqrt( 1 + self.k ) ]; % apex at the orgin 90 | 91 | % rotate and shift 92 | if self.rotang ~= 0 93 | S = rodrigues_rot( S, self.rotax, self.rotang ); 94 | end 95 | x(:) = S( :, 1 ) + self.r( 1 ); 96 | y(:) = S( :, 2 ) + self.r( 2 ); 97 | z(:) = S( :, 3 ) + self.r( 3 ); 98 | 99 | if ~isempty( self.image ) 100 | c = self.image; 101 | else 102 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 103 | end 104 | h = surf( x, y, z, c, ... 105 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 106 | 'AmbientStrength', 1, 'SpecularStrength', 0 ); 107 | colormap summer; 108 | end 109 | 110 | end 111 | 112 | end 113 | 114 | -------------------------------------------------------------------------------- /Screen.m: -------------------------------------------------------------------------------- 1 | classdef Screen < Surface 2 | % SCREEN implements a rectangular screen surface 3 | % Detailed explanation goes here 4 | % 5 | % Member functions: 6 | % 7 | % p = Screen( r, w, h, wbins, hbins ) - object constructor 8 | % INPUT: 9 | % r - 1x3 position vector 10 | % w - width 11 | % h - height 12 | % wbins - number of bins in the horizontal direction 13 | % hbins - number of bins in the vertical direction 14 | % OUTPUT: 15 | % p - screen object 16 | % 17 | % p.display() - displays the screen p information 18 | % 19 | % p.draw() - draws the screen p in the current axes 20 | % 21 | % p.rotate( rot_axis, rot_angle ) - rotate the screen p 22 | % INPUT: 23 | % rot_axis - 1x3 vector defining the rotation axis 24 | % rot_angle - rotation angle (radians) 25 | % 26 | % Copyright: Yury Petrov, 2016 27 | % 28 | 29 | properties 30 | h = 1; % height 31 | w = 1; % width 32 | hbins = 128; % number of bins along y-axis 33 | wbins = 128; % number of bins along x-axis 34 | image = []; % image on the screen 35 | end 36 | 37 | methods 38 | function self = Screen( ar, aw, ah, awbins, ahbins ) 39 | if nargin == 0 40 | return; 41 | end 42 | self.R = Inf; 43 | self.k = 0; 44 | self.r = ar; 45 | self.h = ah; 46 | self.w = aw; 47 | self.hbins = round( ahbins ); 48 | self.wbins = round( awbins ); 49 | end 50 | 51 | function display( self ) 52 | % describe self 53 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 54 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 55 | if ~isinf( self.R ) 56 | fprintf( 'Curv. radius:\t %.3f\n', self.R ); 57 | end 58 | if self.R ~= 0 59 | fprintf( 'Conic coefficient:\t %.3f\n', self.k ); 60 | end 61 | fprintf( 'Width:\t %.3f\n', self.w ); 62 | fprintf( 'Height:\t %.3f\n', self.h ); 63 | fprintf( 'Width bins:\t %i\n', self.wbins ); 64 | fprintf( 'Height bins:\t %i\n', self.hbins ); 65 | fprintf( 'Rotation axis:\t [%.3f %.3f %.3f]\n', self.rotax ); 66 | fprintf( 'Rotation angle:\t %.3f\n', self.rotang ); 67 | end 68 | 69 | function hndl = draw( self, color ) 70 | if nargin < 2 71 | color = [ .2 .2 .2 1 ]; 72 | end 73 | % draw self 74 | y = linspace( -self.w/2, self.w/2, self.wbins ); 75 | z = linspace( -self.h/2, self.h/2, self.hbins ); 76 | [ y, z ] = meshgrid( y, z ); 77 | if isinf( self.R ) 78 | x = zeros( size( y ) ); 79 | else 80 | r2yz = ( y.^2 + z.^2 ) / self.R^2; 81 | a = 1 + self.k; 82 | if a == 0 % paraboloid, special case 83 | x = r2yz * self.R / 2; 84 | else 85 | x = self.R * r2yz ./ ( 1 + sqrt( 1 - a * r2yz ) ); 86 | end 87 | end 88 | S = [ x(:) y(:) z(:) ]; 89 | 90 | % rotate and shift 91 | if self.rotang ~= 0 92 | S = rodrigues_rot( S, self.rotax, self.rotang ); 93 | end 94 | x(:) = S( :, 1 ) + self.r( 1 ); 95 | y(:) = S( :, 2 ) + self.r( 2 ); 96 | z(:) = S( :, 3 ) + self.r( 3 ); 97 | 98 | % draw 99 | if isempty( self.image ) 100 | % c = 0.2 * ones( size( z, 1 ), size( z, 2 ) ); 101 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 102 | else 103 | c = self.image; 104 | end 105 | c = flipud( c ); % to get from image to matrix form 106 | c = fliplr( c ); % because y-axis points to the left 107 | hndl = surf( x, y, z, c, 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', ... 108 | 'AmbientStrength', 0., 'SpecularStrength', 0 ); % dull 109 | colormap summer; 110 | end 111 | 112 | end 113 | 114 | end 115 | 116 | -------------------------------------------------------------------------------- /lens_dims.m: -------------------------------------------------------------------------------- 1 | function [ ht, hf, hb, V ] = lens_dims( Df, Db, Rf, Rb, kf, kb, h, af, ab, disp_fl ) 2 | % LENS_DIMS calculates a lens thickness and volume given its surface 3 | % parameters, draws the lens and save the animation in lens_dims.gif 4 | % Volume is calculated correctly only for lenses without astigmatism. 5 | % 6 | % INPUT: 7 | % Df, Db - lens diameter(s), front and back 8 | % Rf - radius of the tangent sphere for the front surface 9 | % Rb - radius of the tangent sphere for the back surface 10 | % kf - asphreicity coefficient for the front surface 11 | % kb - asphreicity coefficient for the back surface 12 | % h - lens height at the equator (conical part) 13 | % af - aspheric (polynomial) terms for the front surface 14 | % ab - aspheric (polynomial) terms for the bakc surface 15 | % disp_fl - (1, default) display the lens and save its animation in lens_dims.gif, (0) do not 16 | % dispaly the lens 17 | % 18 | % OUTPUT: 19 | % ht - total lens thickness 20 | % hf - front part thickness 21 | % hb - back part thickness 22 | % V - lens volume 23 | % 24 | % Copyright: Yury Petrov, 2016 25 | % 26 | 27 | if nargin < 10 28 | disp_fl = 1; 29 | end 30 | if nargin < 9 31 | ab = []; 32 | end 33 | if nargin < 8 34 | af = []; 35 | end 36 | if nargin < 7 37 | h = 0; 38 | end 39 | 40 | if length( Df ) == 1 41 | Df = [ 0 Df ]; 42 | end 43 | if length( Db ) == 1 44 | Db = [ 0 Db ]; 45 | end 46 | 47 | 48 | % get volumes and heights at the center 49 | nr = 10000; 50 | r = linspace( Df(1)/2, Df(2)/2, nr ); 51 | dr = ( Df(2) - Df(1) ) / 2 / nr; 52 | sf = aspheric( [ Rf(1) Rf(1) kf af ], r ); 53 | hf = sf( end ); 54 | Vf = sum( 2 * pi * dr * ( r .* sf ) ); % numeric integration 55 | if sf( end ) > sf( 1 ) % curving away 56 | Vf = pi * ( r( end )^2 - r(1)^2 ) * hf - Vf; 57 | end 58 | r = linspace( Db(1)/2, Db(2)/2, nr ); 59 | dr = ( Db(2) - Db(1) ) / 2 / nr; 60 | sb = aspheric( [ -Rb(1) -Rb(1) kb ab ], r ); 61 | hb = sb( end ); 62 | Vb = sum( 2 * pi * dr * ( r .* sb ) ); % numeric integration 63 | if sb( end ) > sb( 1 ) % curving away 64 | Vb = pi * ( r( end )^2 - r(1)^2 ) * hb - Vb; 65 | end 66 | 67 | Dmax = max( Df(2), Db(2) ); 68 | Dmin = min( Df(2), Db(2) ); 69 | R = Dmax / 2; % base radius 70 | r = Dmin / 2; % top radius 71 | Ve = pi/3 * h * ( R^2 + R * r + r^2 ); % conical volume at the equator 72 | dmax = max( Df(1), Db(1) ); 73 | dmin = min( Df(1), Db(1) ); 74 | R = dmax / 2; % base radius for inside cut 75 | r = dmin / 2; % top radius for inside cut 76 | Ve = Ve - pi/3 * h * ( R^2 + R * r + r^2 ); % conical volume 0f the inside cut at the equator 77 | 78 | ht = hf + hb + h; % total lens thickness in the center 79 | V = Vf + Vb + Ve; 80 | 81 | if disp_fl ~= 0 % create an animated gif 82 | % draw the lens 83 | bench = Bench(); 84 | lensf = AsphericLens( [ -hf - h/2 0 0 ], Df, Rf, kf, af, { 'air', 'air' } ); 85 | bench.append( lensf ); 86 | % draw the outside wall 87 | if Df(2) == Db(2) 88 | lensh = CylinderLens( [ -h/2 0 0 ], Df(2), h, { 'air', 'air' } ); 89 | else 90 | lensh = ConeLens( [ -h/2 0 0 ], Df(2), h, atan( ( Db(2) - Df(2) ) / 2 / h ), { 'air', 'air' } ); 91 | end 92 | bench.append( lensh ); 93 | if Df(1) ~= 0 || Db(1) ~= 0 % draw the inside wall 94 | hi = ht - sf(1) - sb(1); 95 | if Df(1) == Db(1) 96 | lensi = CylinderLens( [ -hf - h/2 + sf(1) 0 0 ], Df(1), hi, { 'air', 'air' } ); 97 | else 98 | lensi = ConeLens( [ -hf - h/2 + sf(1) 0 0 ], Df(1), hi, atan( ( Db(1) - Df(1) ) / 2 / hi ), { 'air', 'air' } ); 99 | end 100 | bench.append( lensi ); 101 | end 102 | lensb = AsphericLens( [ h/2 + hb 0 0 ], Db, Rb, kb, ab, { 'air', 'air' } ); 103 | bench.append( lensb ); 104 | bench.draw( [], [], 0.33 ); % draw the lens with opaqueness set to 0.5 105 | camlight( 'left' ); 106 | camlight( 'right' ); 107 | camlight( 'headlight' ); 108 | view( 0, 0 ); 109 | 110 | filename = 'lens_dims.gif'; 111 | for i = 1 : 36 112 | camorbit( 10, 0 ); 113 | frame = getframe(1); 114 | im = frame2im( frame ); 115 | [ imind, cm ] = rgb2ind( im, 256 ); 116 | if i == 1 117 | imwrite( imind, cm, filename, 'gif', 'Loopcount', inf ); 118 | else 119 | imwrite( imind, cm, filename, 'gif', 'WriteMode', 'append' ); 120 | end 121 | pause( .02 ); 122 | end 123 | end 124 | 125 | end 126 | 127 | -------------------------------------------------------------------------------- /export_stl.m: -------------------------------------------------------------------------------- 1 | function export_stl( lens1, lens2, centerThickness, nang, flange, filename ) 2 | % EXPORT_LENS exports the lens shape in STL format, requires stlwrite 3 | % installed in the Matlab path 4 | % 5 | % INPUT: 6 | % lens1 - first lens surface lens object 7 | % lens2 - second lens surface lens object 8 | % centerThickness - thickness of the lens along the axis 9 | % nang - number of angular segments in a circle 10 | % flange - a 1 x 2 vector of flange heights 11 | % filename - name of the exported STL file 12 | % 13 | % Copyright: Yury Petrov, 2018 14 | % 15 | 16 | if nargin < 4 17 | nang = 100; 18 | end 19 | if nargin < 5 20 | flange = []; 21 | end 22 | if nargin < 6 || isempty( filename ) || strcmp( filename, '' ) 23 | filename = [ datestr( now, 'mmddyyyyHHMMSS' ) '.stl' ]; 24 | end 25 | 26 | as = linspace( 0, 2 * pi, nang ); 27 | 28 | %get lens profile 29 | npoints = 1000; 30 | if isprop( lens1, 'ncones' ) || isprop( lens2, 'ncones' ) % a Fresnel structure 31 | npoints = 5000; 32 | % npoints = round( D(1) / 2 * 1000 ); % to get radius steps in microns 33 | end 34 | 35 | Y1 = linspace( lens1.D(1)/2, lens1.D(2)/2, npoints ); % starting from the center 36 | Y2 = linspace( lens2.D(1)/2, lens2.D(2)/2, npoints ); % starting from the center 37 | 38 | if isprop( lens1, 'R' ) && ~isempty( lens1.R ) 39 | R1 = lens1.R; 40 | if length( R1 ) < 2 41 | R1 = [ R1 R1 ]; 42 | end 43 | pars1 = [ R1(1) R1(2) lens1.k(1) ]; 44 | if isprop( lens1, 'avec' ) && ~isempty( lens1.avec ) 45 | pars1 = [ pars1 lens1.avec( :, 1 )' ]; 46 | end 47 | else 48 | pars1 = [ Inf Inf 0 ]; % planar surface 49 | end 50 | 51 | if isprop( lens2, 'R' ) && ~isempty( lens2.R ) 52 | R2 = lens2.R; 53 | if length( R2 ) < 2 54 | R2 = [ R2 R2 ]; 55 | end 56 | pars2 = [ R2(1) R2(2) lens2.k(1) ]; 57 | if isprop( lens2, 'avec' ) && ~isempty( lens2.avec ) 58 | pars2 = [ pars2 lens2.avec( :, 1 )' ]; 59 | end 60 | else 61 | pars2 = [ Inf Inf 0 ]; % planar surface 62 | end 63 | 64 | r = [ 0 centerThickness ]; 65 | for i = 1 : nang 66 | cs = cos( as( i ) ); 67 | sn = sin( as( i ) ); 68 | 69 | if ~isprop( lens1, 'ncones' ) % a non-Fresnel lens 70 | y1 = Y1; 71 | if ~isprop( lens1, 'funch' ) % not a general lens 72 | if isinf( pars1(1) ) && isinf( pars1(2) ) % a plane 73 | p1 = zeros( size( Y1 ) ); 74 | else 75 | p1 = asphlens( Y1 * cs, Y1 * sn, pars1, 0 ); 76 | end 77 | else 78 | p1 = lens1.funch( Y1 * cs, Y1 * sn, lens1.funca, 0 ); 79 | end 80 | else % a Fresnel lens surface 81 | [ p1, y1 ] = frenlens( Y1, lens1 ); 82 | end 83 | 84 | if ~isprop( lens2, 'ncones' ) % a non-Fresnel lens 85 | y2 = Y2; 86 | if ~isprop( lens2, 'funch' ) % not a general lens 87 | if isinf( pars2(1) ) && isinf( pars2(2) ) % a plane 88 | p2 = zeros( size( Y2 ) ); 89 | else 90 | p2 = asphlens( Y2 * cs, Y2 * sn, pars2, 0 ); 91 | end 92 | else 93 | p2 = lens1.funch( Y2 * cs, Y2 * sn, lens2.funca, 0 ); 94 | end 95 | else % a Fresnel lens surface 96 | [ p2, y2 ] = frenlens( Y2, lens2 ); 97 | end 98 | 99 | if isempty( flange ) 100 | tmp = [ -fliplr( y1 ), y1, fliplr( y2 ), -y2, -y1( end ) ]; 101 | z( i, : ) = tmp * sn; 102 | y( i, : ) = tmp * cs; 103 | x( i, : ) = [ r( 1 ) + fliplr( p1 ), r( 1 ) + p1, r( 2 ) + fliplr( p2 ), r( 2 ) + p2, r( 1 ) + p1( end ) ]; 104 | else 105 | tmp = [ -fliplr( y1 ), y1, y1( end ) + flange(1), y2( end ) + flange(2), fliplr( y2 ), -y2, -y2( end ) - flange(2), -y1(end) - flange(1), -y1( end ) ]; 106 | z( i, : ) = tmp * sn; 107 | y( i, : ) = tmp * cs; 108 | x( i, : ) = [ r(1) + fliplr( p1 ), r(1) + p1, r(1) + p1( end ), r(2) + p2( end ), r(2) + fliplr( p2 ), r(2) + p2, r(2) + p2( end ), r(1) + p1( end ), r(1) + p1( end ) ]; 109 | end 110 | end 111 | 112 | %draw & export 113 | figure; 114 | h = surf( z, y, x, 'LineStyle','none', 'FaceColor', 'interp', 'FaceAlpha', 0.5 ); 115 | axis vis3d equal; 116 | 117 | % export stl of the lens profile 118 | if exist( 'stlwrite', 'file') 119 | [ f, v ] = surf2patch( h, 'triangles' ); 120 | TP = triangulation( f, v ); 121 | stlwrite( TP, filename ); % this is a build-in function in Matlab 2018b 122 | fprintf( 'Exported to %s\n', filename ); 123 | end 124 | -------------------------------------------------------------------------------- /Aperture.m: -------------------------------------------------------------------------------- 1 | classdef Aperture < Surface 2 | % APERTURE defines a circular or rectangular opening 3 | % 4 | % Member functions: 5 | % 6 | % a = Aperture( r, D ) - object constructor 7 | % INPUT: 8 | % r - 1x3 position vector 9 | % D - 2x1 vector (inner diameter, outer diameter) or 4x1 vector (inner 10 | % w, innher h, outer w, outer h) 11 | % OUTPUT: 12 | % a - aperture object 13 | % 14 | % a.display() - displays the aperture a's information 15 | % 16 | % draw() - draws the aperture a in the current axes 17 | % 18 | % a.rotate( rot_axis, rot_angle ) - rotate the aperture 19 | % INPUT: 20 | % rot_axis - 1x3 vector defining the rotation axis 21 | % rot_angle - rotation angle (radians) 22 | % 23 | % Copyright: Yury Petrov, 2016 24 | % 25 | 26 | properties 27 | D = [ 1; 2 ] 28 | end 29 | 30 | methods 31 | function self = Aperture( ar, aD ) 32 | self.glass = { 'air' 'soot' }; 33 | if nargin == 0 34 | return; 35 | end 36 | self.r = ar; 37 | if size( aD, 1 ) < size( aD, 2 ) 38 | aD = aD'; 39 | end 40 | self.D = aD; 41 | if size( self.D, 1 ) ~= 2 && size( self.D, 1 ) ~= 4 42 | error( 'Aperture dimensions have to be a 2x1 or 4x1 vector' ); 43 | elseif size( self.D, 1 ) == 2 && self.D(2) < self.D(1) 44 | error( 'Outer radius has to be larger than the inner radius' ); 45 | elseif size( self.D, 1 ) == 4 && ( self.D(1) > self.D(3) || self.D(2) > self.D(4) ) 46 | error( 'Outer aperture dimensions have to be larger than inner dimensions' ); 47 | end 48 | end 49 | 50 | function display( self ) 51 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 52 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 53 | fprintf( 'Diameter in:\t %.3f\n', self.D(1) ); 54 | fprintf( 'Diameter out:\t %.3f\n', self.D(2) ); 55 | end 56 | 57 | function h = draw( self, color ) 58 | if size( self.D, 1 ) == 2 % circular aperture 59 | nrad = 2; 60 | rad = linspace( self.D(1) / 2, self.D(2) / 2, nrad ); 61 | nang = 100; 62 | ang = linspace( 0, 2 * pi, nang ); 63 | [ ang, rad ] = meshgrid( ang, rad ); 64 | 65 | y = rad .* cos( ang ); 66 | z = rad .* sin( ang ); 67 | x = zeros( size( y ) ); 68 | S = [ x(:) y(:) z(:) ]; 69 | 70 | % rotate and shift 71 | if self.rotang ~= 0 72 | S = rodrigues_rot( S, self.rotax, self.rotang ); 73 | end 74 | x(:) = S( :, 1 ) + self.r( 1 ); 75 | y(:) = S( :, 2 ) + self.r( 2 ); 76 | z(:) = S( :, 3 ) + self.r( 3 ); 77 | % draw 78 | c = 0.25 * ones( size( x, 1 ), size( x, 2 ), 3 ); 79 | h = surf( x, y, z, c, 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', ... 80 | 'AmbientStrength', 0.25, 'SpecularStrength', 0.25 ); 81 | else % rectangular aperture 82 | w = self.D(1); 83 | h = self.D(2); 84 | W = self.D(3); 85 | H = self.D(4); 86 | V = [ 0 0 0 87 | 0 W 0 88 | 0 W H 89 | 0 0 H ]; 90 | v = [ 0 0 0 91 | 0 w 0 92 | 0 w h 93 | 0 0 h ]; 94 | v( :, 2 ) = v( :, 2 ) + ( W - w )/2; 95 | v( :, 3 ) = v( :, 3 ) + ( H - h )/2; 96 | v = [ V; v ]; 97 | v( :, 1 ) = v( :, 1 ) + self.r( 1 ); 98 | v( :, 2 ) = v( :, 2 ) + self.r( 2 ) - W/2; 99 | v( :, 3 ) = v( :, 3 ) + self.r( 3 ) - H/2; 100 | 101 | f = [ 1 2 6 5 102 | 2 3 7 6 103 | 3 4 8 7 104 | 4 1 5 8 ]; 105 | 106 | % rotate and shift 107 | S = [ v( :, 1 ) v( :, 2 ) v( :, 3 ) ]; 108 | if self.rotang ~= 0 109 | S = rodrigues_rot( S, self.rotax, self.rotang ); 110 | end 111 | v = S + repmat( self.r, size( v, 1 ), 1 ); 112 | 113 | % draw 114 | h = patch( 'Faces', f, 'Vertices', v, 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', [ .25 .25 .25 ], ... 115 | 'AmbientStrength', 0.25, 'SpecularStrength', 0.25); 116 | end 117 | end 118 | end 119 | 120 | end 121 | 122 | -------------------------------------------------------------------------------- /Plane.m: -------------------------------------------------------------------------------- 1 | classdef Plane < Surface 2 | % PLANE implements a planar refracting or reflecting surface, e.g. one 3 | % face of a prism or a mirror 4 | % 5 | % Member functions: 6 | % 7 | % p = Plane( r, D, glass ) - circular planar surface constructor 8 | % INPUT: 9 | % r - 1x3 position vector 10 | % D - diameter, 1x1 vector (outer) or 2x1 vector (inner, outer) 11 | % glass - 1 x 3 cell array of two strings, e.g., { 'air' 'acrylic' } 12 | % OUTPUT: 13 | % p - plane object 14 | % 15 | % p = Plane( r, w, h, glass ) - rectangular planar surface constructor 16 | % INPUT: 17 | % r - 1x3 position vector 18 | % w - width 19 | % h - height 20 | % glass - 1 x 3 cell array of two strings, e.g., { 'air' 'acrylic' } 21 | % OUTPUT: 22 | % p - plane object 23 | % 24 | % p.display() - displays the plane p information 25 | % 26 | % p.draw() - draws the plane p in the current axes 27 | % 28 | % p.rotate( rot_axis, rot_angle ) - rotate the plane p 29 | % INPUT: 30 | % rot_axis - 1x3 vector defining the rotation axis 31 | % rot_angle - rotation angle (radians) 32 | % 33 | % Copyright: Yury Petrov, 2016 34 | % 35 | 36 | properties 37 | w = [] % width of the surface 38 | h = [] % height of the surface 39 | D = [] % lens diameter (inner, outer) 40 | end 41 | 42 | methods 43 | function self = Plane( varargin ) % some function overloading here 44 | if nargin == 0 45 | return; 46 | elseif nargin == 3 47 | self.r = varargin{1}; 48 | aD = varargin{2}; 49 | if size( aD, 1 ) < size( aD, 2 ) 50 | aD = aD'; 51 | end 52 | if size( aD, 1 ) == 1 53 | aD = [ 0; aD ]; 54 | end 55 | self.D = aD; 56 | self.glass = varargin{3}; 57 | elseif nargin == 4 58 | self.r = varargin{1}; 59 | self.w = varargin{2}; 60 | self.h = varargin{3}; 61 | self.glass = varargin{4}; 62 | end 63 | end 64 | 65 | function display( self ) 66 | % describe self 67 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 68 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 69 | if ~isempty( self.w ) && ~isempty( self.h ) 70 | fprintf( 'Width:\t %.3f\n', self.w ); 71 | fprintf( 'Height:\t %.3f\n', self.h ); 72 | elseif ~isempty( self.D ) 73 | fprintf( 'Diameter:\t %.3f\n', self.D(2) ); 74 | if self.D(1) ~= 0 75 | fprintf( 'Inner diameter:\t %.3f\n', self.D(1) ); 76 | end 77 | end 78 | fprintf( 'Rotation axis:\t [%.3f %.3f %.3f]\n', self.rotax ); 79 | fprintf( 'Rotation angle:\t %.3f\n', self.rotang ); 80 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 81 | end 82 | 83 | function h = draw( self, color ) 84 | % draw self 85 | if nargin < 2 86 | color = [ 1 1 1 .5 ]; 87 | end 88 | if ~isempty( self.w == 0 ) && ~isempty( self.h ) 89 | y = [-self.w/2 self.w/2 ]; 90 | z = [-self.h/2 self.h/2 ]; 91 | [ y, z ] = meshgrid( y, z ); 92 | elseif ~isempty( self.D ) 93 | nrad = 50; 94 | if length( self.D ) == 1 95 | rad = linspace( 0, self.D / 2, nrad ); 96 | else 97 | rad = linspace( self.D(1)/2, self.D(2) / 2, nrad ); 98 | end 99 | nang = 50; 100 | ang = linspace( 0, 2 * pi, nang ); 101 | [ ang, rad ] = meshgrid( ang, rad ); 102 | [ y, z ] = pol2cart( ang, rad ); 103 | end 104 | x = zeros( size( y ) ); 105 | S = [ x(:) y(:) z(:) ]; 106 | 107 | % rotate and shift 108 | if self.rotang ~= 0 109 | S = rodrigues_rot( S, self.rotax, self.rotang ); 110 | end 111 | x(:) = S( :, 1 ) + self.r( 1 ); 112 | y(:) = S( :, 2 ) + self.r( 2 ); 113 | z(:) = S( :, 3 ) + self.r( 3 ); 114 | 115 | % draw 116 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 117 | h = surf( x, y, z, c, ... 118 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 119 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 120 | end 121 | 122 | end 123 | 124 | end 125 | 126 | -------------------------------------------------------------------------------- /Lens.m: -------------------------------------------------------------------------------- 1 | classdef Lens < Surface 2 | % LENS Implements a lens surface given by a rotation of a conic curve 3 | % (conic) lens surface given by 4 | % z = 1/R * r^2 ./ ( 1 + sqrt( 1 - ( 1 + k ) * (r/R)^2 ) ), where 5 | % R is the tangent sphere radius, and k is the aspheric factor: 6 | % 0 < k - oblate spheroid 7 | % k = 0 - sphere 8 | % -1 < k < 0 - prolate spheroid 9 | % k = -1 - parabola 10 | % k < -1 - hyperbola 11 | % 12 | % Member functions: 13 | % 14 | % l = Lens( r, D, R, k, glass ) - object constructor 15 | % INPUT: 16 | % r - 1x3 position vector 17 | % D - diameter, 1x1 vector (outer) or 2x1 vector (inner, outer) 18 | % R - tangent sphere radius, [ Ry Rz ] vector for an astigmatic surface 19 | % k - conic coefficient, for astigmatic surface corresponds to the y-axis 20 | % glass - 1 x 2 cell array of strings, e.g., { 'air' 'acrylic' } 21 | % OUTPUT: 22 | % l - lens surface object 23 | % 24 | % l.display() - displays the surface l information 25 | % 26 | % l.draw() - draws the surface l in the current axes 27 | % 28 | % l.rotate( rot_axis, rot_angle ) - rotate the surface l 29 | % INPUT: 30 | % rot_axis - 1x3 vector defining the rotation axis 31 | % rot_angle - rotation angle (radians) 32 | % 33 | % Copyright: Yury Petrov, 2016 34 | % 35 | 36 | properties 37 | D = [ 0; 1 ]; % lens diameter (inner, outer) 38 | end 39 | 40 | methods 41 | function self = Lens( ar, aD, aR, ak, aglass ) 42 | if nargin == 0 43 | return; 44 | end 45 | if size( aD, 1 ) < size( aD, 2 ) 46 | aD = aD'; 47 | end 48 | if size( aD, 1 ) == 1 49 | aD = [ 0; aD ]; 50 | end 51 | self.r = ar; 52 | self.D = aD; 53 | self.R = aR; 54 | self.k = ak; 55 | self.glass = aglass; 56 | if ( self.D(2) / 2 / self.R(1) )^2 * ( 1 + self.k ) > 1 57 | % error( 'Lens Diameter is too large for its radius and apsheric parameter!' ); 58 | self.D(1) = -1; % signal bad parameters 59 | end 60 | end 61 | 62 | function display( self ) 63 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 64 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 65 | fprintf( 'Diameter:\t %.3f\n', self.D(2) ); 66 | if self.D(1) ~= 0 67 | fprintf( 'Inner diameter:\t %.3f\n', self.D(1) ); 68 | end 69 | if length( self.R ) == 1 70 | fprintf( 'Curv. radius:\t %.3f\n', self.R ); 71 | else 72 | fprintf( 'Y Curv. radius:\t %.3f\n', self.R(1) ); 73 | fprintf( 'Z Curv. radius:\t %.3f\n', self.R(2) ); 74 | end 75 | fprintf( 'Conic coefficient:\t %.3f\n', self.k ); 76 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 77 | end 78 | 79 | function h = draw( self, color ) 80 | % DISPLAY the lens surface 81 | if nargin < 2 82 | color = [ 1 1 1 .5 ]; 83 | end 84 | nrad = 50; 85 | rad = linspace( self.D(1) / 2, self.D(2) / 2, nrad ); 86 | nang = 100; 87 | ang = linspace( 0, 2 * pi, nang ); 88 | [ ang, rad ] = meshgrid( ang, rad ); 89 | 90 | [ y, z ] = pol2cart( ang, rad ); 91 | a = 1 + self.k; 92 | if length( self.R ) == 1 93 | r2yz = ( y.^2 + z.^2 ) / self.R^2; 94 | if a == 0 % paraboloid, special case 95 | x = r2yz * self.R / 2; 96 | else 97 | x = self.R * r2yz ./ ( 1 + sqrt( 1 - a * r2yz ) ); 98 | end 99 | else % asymmetric conic 100 | r2yz = y.^2 / self.R(1) + z.^2 / self.R(2); 101 | if a == 0 % paraboloid, special case 102 | x = r2yz / 2; 103 | else 104 | x = r2yz ./ ( 1 + sqrt( 1 - a * ( y.^2 / self.R(1)^2 + self.R(2) / self.R(1) * z.^2 / self.R(2)^2 ) ) ); 105 | end 106 | end 107 | 108 | 109 | S = [ x(:) y(:) z(:) ]; 110 | 111 | % rotate and shift 112 | if self.rotang ~= 0 113 | S = rodrigues_rot( S, self.rotax, self.rotang ); 114 | end 115 | x(:) = S( :, 1 ) + self.r( 1 ); 116 | y(:) = S( :, 2 ) + self.r( 2 ); 117 | z(:) = S( :, 3 ) + self.r( 3 ); 118 | 119 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 120 | h = surf( x, y, z, c, ... 121 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 122 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 123 | end 124 | 125 | end 126 | 127 | end 128 | 129 | -------------------------------------------------------------------------------- /refrindx.m: -------------------------------------------------------------------------------- 1 | function [ n_refr, dens, hard ] = refrindx( wavelength, glass ) 2 | % REFRINDX refractive index calculations. 3 | % 4 | % INPUT: 5 | % wavelength - light wavelength in meters 6 | % glass - material string 7 | % 8 | % OUTPUT: 9 | % n_refr - refractive index for the wavelengths: 587.6, 486.1, 656.3 nm 10 | % dens - material density in g/cm^3 11 | % hard - abrasion resistance from 1 to 10 (Mohs hardness) 12 | % 13 | % Data from JML Optical Industries, Inc available at: 14 | % http://www.netacc.net/~jmlopt/transmission2.html 15 | % Or at: 16 | % http://www.jmlopt.com/level2/TechInfo/materialstable.aspx 17 | % 18 | % The human eye data are from in Escudero-Sanz & Navarro, 19 | % "Off-axis aberrations of a wide-angle schematic eye model", 20 | % JOSA A, 16(8), 1881-1891 (1999). 21 | 22 | persistent glass_names_sellmeier refr_consts_sellmeier 23 | 24 | if isempty( wavelength ) 25 | wavelength = 5876e-10; % green 26 | end 27 | 28 | n_refr = []; 29 | dens = []; 30 | hard = []; 31 | 32 | if isempty( glass_names_sellmeier ) 33 | qwe = ''; 34 | fp = fopen( 'Sellmeier.glass.refr', 'r' ); 35 | while ~feof( fp ) 36 | qwe = str2mat( qwe, fgetl( fp ) ); 37 | end 38 | glass_names_sellmeier = qwe( 2:end, 1:7 ); 39 | refr_consts_sellmeier = str2num( qwe( 2:end, 8:end ) ); 40 | end 41 | 42 | lambda_ref = [ 5876 4861 6563 ] * 1e-10; 43 | lambda_eye = [4580 5430 5893 6328] * 1e-10; 44 | 45 | if isempty( wavelength ) 46 | wavelength = lambda_ref( 1 ); % green wavelength by default 47 | end 48 | 49 | ind = strmatch( glass, glass_names_sellmeier ); 50 | if ~isempty( ind ) 51 | lambda = wavelength * 1e6; % change units to micrometer 52 | A = refr_consts_sellmeier( ind, 1:3 ); 53 | B = refr_consts_sellmeier( ind, 4:6 ); 54 | n_refr = sqrt( 1 + A(1) * lambda.^2 ./ ( lambda.^2 - B(1) ) + ... 55 | A(2) * lambda.^2 ./ ( lambda.^2 - B(2) ) + ... 56 | A(3) * lambda.^2 ./ ( lambda.^2 - B(3) ) ); 57 | else 58 | switch lower( glass ) 59 | case 'air' 60 | nref = [ 1 1 1 ]; 61 | dens = 0.001225; 62 | hard = 0; 63 | case 'water' 64 | nref = [ 1.3325 1.3356 1.3310 ]; 65 | dens = 1.; 66 | hard = 0; 67 | case 'ice' 68 | nref = [ 1.3098 1.3137 1.3079 ]; 69 | dens = 0.9167; 70 | hard = 1.5; 71 | case { 'quartz' 'SiO2' } 72 | nref = [ 1.4585 1.4631 1.4564 ]; 73 | dens = 2.648; 74 | hard = 7; 75 | case 'diamond' 76 | nref = [ 2.4168 2.4305 2.4088 ]; 77 | dens = 3.51; 78 | hard = 10; 79 | case { 'mirror' 'soot' } 80 | nref = [ 1 1 1 ]; 81 | % plastics 82 | case { 'pmma', 'acrylic' } 83 | nref = [ 1.491 1.496 1.488 ]; 84 | dens = 1.185; 85 | hard = 10; 86 | case { 'pc', 'polycarbonate' } 87 | nref = [ 1.5849 1.5994 1.5782 ]; 88 | dens = 1.21; 89 | hard = 2; 90 | case { 'ps', 'polystyrene' } 91 | nref = [ 1.5917 1.6056 1.5853 ]; 92 | dens = 1.06; 93 | hard = 4; 94 | case { 'nas-21' 'nas' } 95 | nref = [ 1.5714 1.5835 1.5669 ]; 96 | dens = 1.09; 97 | hard = 6; 98 | case { 'optorez-1330' 'optorez' } 99 | nref = [ 1.5094 1.5163 1.5067 ]; 100 | dens = 1.19; 101 | case { 'zeonex-e48r' 'zeonex' } 102 | nref = [ 1.5309 1.5376 1.5273 ]; 103 | dens = 1.01; 104 | hard = 10; 105 | case 'optimas6000' 106 | nref = [1.5013 1.5061 1.4969]; 107 | dens = 1.08; 108 | hard = 10; 109 | case { 'r-5000' 'polysulfone' 'psu' } 110 | nref = [ 1.675 1.696 1.660]; 111 | dens = 1.24; 112 | % glasses 113 | case 'b270' 114 | nref = [1.5230 1.5292 1.5202]; 115 | dens = 2.55; 116 | case 'bak1' 117 | nref = [1.5725 1.5794 1.5694]; 118 | dens = 3.19; 119 | case 'bak2' 120 | nref = [1.5399 1.5462 1.5372]; 121 | case 'bak4' 122 | nref = [1.56883 1.5759 1.56576]; 123 | dens = 3.10; 124 | case 'balkn3' 125 | nref = [1.51849 1.52447 1.51586]; 126 | case 'bk7' 127 | nref = [1.5168 1.5224 1.5143]; 128 | dens = 2.51; 129 | case 'f2' 130 | nref = [1.6200 1.6320 1.6150]; 131 | dens = 3.60; 132 | case 'f3' 133 | nref = [1.61293 1.62461 1.60806]; 134 | case 'f4' 135 | nref = [1.6165 1.6284 1.6116]; 136 | case 'fusedsilica' 137 | nref = [1.458 1.463 1.456]; 138 | dens = 2.20; 139 | case 'k5' 140 | nref = [1.5224 1.5285 1.5198]; 141 | dens = 2.59; 142 | case 'k7' 143 | nref = [1.51112 1.517 1.50854]; 144 | case 'lasfn9' 145 | nref = [1.850 1.8689 1.8425]; 146 | dens = 4.44; 147 | case 'lah71' 148 | nref = [1.8502 1.8689 1.8425]; 149 | case 'n-bk7' 150 | nref = [1.5197 1.5239 1.5156]; 151 | case 'pyrex' 152 | nref = [1.473 1.478 1.471]; 153 | case 'sapphire' 154 | nref = [1.7682 1.7756 1.7649]; 155 | dens = 3.97; 156 | case 'sf1' 157 | nref = [1.71736 1.73462 1.71031]; 158 | dens = 3.66; 159 | case 'sf2' 160 | nref = [1.6476 1.6612 1.6421]; 161 | dens = 3.86; 162 | case 'sf5' 163 | nref = [1.6727 1.6875 1.66661]; 164 | dens = 4.07; 165 | case 'sf8' 166 | nref = [1.6889 1.7046 1.6825]; 167 | dens = 4.22; 168 | case 'sf10' 169 | nref = [1.72825 1.74648 1.72085]; 170 | dens = 4.28; 171 | case 'sf11' 172 | nref = [1.7847 1.8064 1.7759]; 173 | dens = 5.41; 174 | case 'sf12' 175 | nref = [1.64831 1.66187 1.64271]; 176 | case 'sf15' 177 | nref = [1.69895 1.71546 1.69221]; 178 | dens = 2.92; 179 | case 'sf18' 180 | nref = [1.7215 1.7390 1.7143]; 181 | dens = 4.49; 182 | case 'sf19' 183 | nref = [1.6668 1.6811 1.6609]; 184 | case 'sf56' 185 | nref = [1.7847 1.8061 1.7760]; 186 | dens = 3.28; 187 | case 'sk3' 188 | nref = [1.6088 1.6160 1.6056]; 189 | case 'sk5' 190 | nref = [1.5891 1.5958 1.5861]; 191 | case 'sk11' 192 | nref = [1.5638 1.5702 1.5610]; 193 | dens = 3.08; 194 | case 'sk16' 195 | nref = [1.6204 1.6275 1.6172]; 196 | case 'ssk2' 197 | nref = [1.6223 1.63048 1.61878]; 198 | case 'ssk4a' 199 | nref = [1.61765 1.62547 1.61427]; 200 | case 'ssk51' 201 | nref = [1.60361 1.61147 1.60022]; 202 | case 'zk5' 203 | nref = [1.53375 1.54049 1.53084]; 204 | % eye 205 | case 'cornea' 206 | nref = [1.3828 1.3777 1.376 1.3747]; 207 | case 'aqueous' 208 | nref = [1.3445 1.3391 1.3374 1.336]; 209 | case 'lens' 210 | nref = [1.4292 1.4222 1.42 1.4183]; 211 | case 'vitreous' 212 | nref = [1.3428 1.3377 1.336 1.3347]; 213 | otherwise 214 | error( [ 'No values for glass absorption for: ', glass ] ); 215 | end 216 | if strcmp( glass, 'cornea' ) || ... 217 | strcmp( glass, 'aqueous' ) || ... 218 | strcmp( glass, 'lens' ) || ... 219 | strcmp( glass, 'vitreous' ) 220 | [ abc, ~, Mu ] = polyfit( 1./lambda_eye.^2, nref, 2 ); 221 | else 222 | [ abc, ~, Mu ] = polyfit( 1./lambda_ref.^2, nref, 2 ); 223 | end 224 | n_refr = polyval( abc, 1./wavelength.^2, [], Mu ); 225 | end 226 | 227 | end 228 | -------------------------------------------------------------------------------- /Sellmeier.glass.refr: -------------------------------------------------------------------------------- 1 | Glass A1 A2 A3 B1 B2 B3 2 | S-FPL51 1.17010505E+00 4.75710783E-02 7.63832445E-01 6.16203924E-03 2.63372876E-02 1.41882642E+02 3 | S-FPL52 1.06785857E+00 3.35857718E-02 1.10219763E+00 6.99227302E-03 -2.07608925E-02 2.26496541E+02 4 | S-FPL53 9.83532327E-01 6.95688140E-02 1.11409238E+00 4.92234955E-03 1.93581091E-02 2.64275294E+02 5 | S-FSL 5 1.17447043E+00 1.40056154E-02 1.19272435E+00 8.41855181E-03 -5.81790767E-02 1.29599726E+02 6 | S-BSL 7 1.15150190E+00 1.18583612E-01 1.26301359E+00 1.05984130E-02 -1.18225190E-02 1.29617662E+02 7 | S-BSM 2 8.67168676E-01 6.72848343E-01 1.18456107E+00 3.69311003E-03 1.81652804E-02 1.32376147E+02 8 | S-BSM 4 9.62443080E-01 5.95939234E-01 1.10558352E+00 4.68062141E-03 1.78772082E-02 1.15896432E+02 9 | S-BSM9 1.37020077E+00 1.89397267E-01 1.24202324E+00 7.57631457E-03 3.00787515E-02 1.31350111E+02 10 | S-BSM10 9.45443081E-01 6.43237376E-01 1.17752968E+00 1.57263798E-02 1.61924066E-03 1.21361748E+02 11 | S-BSM14 1.28286270E+00 2.47647429E-01 1.10383999E+00 1.22902399E-02 -6.13142361E-03 1.06883378E+02 12 | S-BSM15 9.53128328E-01 6.37613977E-01 1.65245647E+00 3.87638985E-03 1.85094632E-02 1.59442367E+02 13 | S-BSM16 1.14490383E+00 4.39563911E-01 1.27688079E+00 1.37034916E-02 -1.86514205E-03 1.19535585E+02 14 | S-BSM18 9.27886025E-01 7.08858526E-01 1.18610897E+00 4.17549199E-03 1.84691838E-02 1.22210416E+02 15 | S-BSM22 1.44305741E+00 1.40786358E-01 1.26093951E+00 8.19208910E-03 3.56911455E-02 1.31959337E+02 16 | S-BSM25 1.34814257E+00 3.47530319E-01 1.38798368E+00 6.95364366E-03 2.77863478E-02 1.42138122E+02 17 | S-BSM28 1.43822841E+00 1.28100017E-01 1.34355530E+00 8.59779750E-03 4.08617854E-02 1.43709890E+02 18 | S-BSM71 1.50847885E+00 1.58099826E-01 1.36815368E+00 8.12769076E-03 3.54200898E-02 1.36110038E+02 19 | S-BSM81 9.96356844E-01 6.51392837E-01 1.22432622E+00 1.44821587E-02 1.54826389E-03 8.99818604E+01 20 | S-NSL 3 8.82514764E-01 3.89271907E-01 1.10693448E+00 4.64504582E-03 2.00551397E-02 1.36234339E+02 21 | S-NSL 5 1.04574577E+00 2.39613026E-01 1.15906850E+00 5.85232280E-03 2.36858752E-02 1.31329061E+02 22 | S-NSL36 1.09666153E+00 1.68990073E-01 1.20580827E+00 6.67491123E-03 3.36095450E-02 1.41668738E+02 23 | S-BAL 2 1.30923813E+00 1.14137353E-01 1.17882259E+00 8.38873953E-03 3.99436485E-02 1.40257892E+02 24 | S-BAL 3 1.29366890E+00 1.32440252E-01 1.10197293E+00 8.00367962E-03 3.54711196E-02 1.34517431E+02 25 | S-BAL11 8.21314256E-01 6.12586478E-01 1.24859637E+00 3.51436131E-03 1.79762375E-02 1.33456670E+02 26 | S-BAL12 7.14605258E-01 6.21993289E-01 1.22537681E+00 3.01763913E-03 1.66505450E-02 1.43506314E+02 27 | S-BAL14 1.27553696E+00 1.46083393E-01 1.16754699E+00 7.49692359E-03 3.10421530E-02 1.28947092E+02 28 | S-BAL35 9.41357273E-01 5.46174895E-01 1.16168917E+00 1.40333996E-02 9.06635683E-04 1.14163758E+02 29 | S-BAL41 1.24344200E+00 1.66301104E-01 1.10586114E+00 1.16396708E-02 -8.90464938E-03 1.14111220E+02 30 | S-BAL42 1.39570615E+00 7.18505070E-02 1.27129267E+00 1.12218843E-02 -2.52117422E-02 1.34497860E+02 31 | S-BAM 3 1.36955358E+00 8.53825867E-02 1.16159771E+00 9.41331434E-03 5.04359027E-02 1.30548899E+02 32 | S-BAM 4 1.41059317E+00 1.11201306E-01 1.34148939E+00 9.63312192E-03 4.98778210E-02 1.52237696E+02 33 | S-BAM12 1.50161605E+00 1.26987445E-01 1.43544052E+00 9.40761826E-03 4.72602195E-02 1.41666499E+02 34 | S-BAH10 1.59034337E+00 1.38464579E-01 1.21988043E+00 9.32734340E-03 4.27498654E-02 1.19251777E+02 35 | S-BAH11 1.57138860E+00 1.47869313E-01 1.28092846E+00 9.10807936E-03 4.02401684E-02 1.30399367E+02 36 | S-BAH27 1.68939052E+00 1.33081013E-01 1.41165515E+00 1.03598193E-02 5.33982239E-02 1.26515503E+02 37 | S-BAH28 1.69493484E+00 1.92890298E-01 1.56385948E+00 1.02723190E-02 5.21187640E-02 1.37818035E+02 38 | S-BAH32 1.58023630E+00 1.37504632E-01 1.60603298E+00 1.03578062E-02 5.48393088E-02 1.47982885E+02 39 | S-PHM52 1.09966550E+00 4.78125422E-01 1.13214074E+00 1.32718559E-02 -6.01649685E-04 1.30595472E+02 40 | S-PHM53 1.09775423E+00 4.34816432E-01 1.13894976E+00 1.23369400E-02 -3.72522903E-04 1.24276984E+02 41 | S-TIL 1 1.25088944E+00 9.97973327E-02 1.20583504E+00 8.83921279E-03 4.82685052E-02 1.37414953E+02 42 | S-TIL 2 1.23401499E+00 9.59796833E-02 1.20503991E+00 8.69507801E-03 4.65611429E-02 1.37953301E+02 43 | S-TIL 6 1.17701777E+00 1.27958030E-01 1.34740124E+00 7.71087686E-03 4.11325328E-02 1.54531692E+02 44 | S-TIL25 1.32122534E+00 1.23824976E-01 1.43685254E+00 9.52091436E-03 5.16062665E-02 1.49064883E+02 45 | S-TIL26 1.31066488E+00 9.41903094E-02 1.23292644E+00 9.68897812E-03 5.27763106E-02 1.33296422E+02 46 | S-TIL27 1.31433154E+00 1.12300168E-01 1.41390100E+00 9.50404477E-03 5.24112772E-02 1.48429972E+02 47 | S-TIM 1 1.44963830E+00 1.22986408E-01 1.38066723E+00 1.12094282E-02 5.96265770E-02 1.38178326E+02 48 | S-TIM 2 1.42193846E+00 1.33827968E-01 1.45060574E+00 1.07291511E-02 5.72587546E-02 1.45381805E+02 49 | S-TIM 3 1.40691144E+00 1.28369745E-01 1.51826191E+00 1.05633641E-02 5.68483105E-02 1.52107924E+02 50 | S-TIM 5 1.38531342E+00 1.22372945E-01 1.40508326E+00 1.04074567E-02 5.57440088E-02 1.44878733E+02 51 | S-TIM 8 1.37262713E+00 1.12636276E-01 1.39786421E+00 1.03220068E-02 5.50195044E-02 1.47735609E+02 52 | S-TIM22 1.44222294E+00 1.94432265E-01 1.74092482E+00 1.04249404E-02 5.50235257E-02 1.69710769E+02 53 | S-TIM25 1.50659233E+00 2.04786135E-01 1.92036668E+00 1.09501562E-02 5.74980285E-02 1.78128535E+02 54 | S-TIM27 1.41680470E+00 1.96785057E-01 1.68001322E+00 1.00732158E-02 5.37616908E-02 1.64672436E+02 55 | S-TIM28 1.54270810E+00 2.17113891E-01 1.81904459E+00 1.13925005E-02 5.79224572E-02 1.67697189E+02 56 | S-TIM35 1.55849775E+00 2.30767007E-01 1.84436099E+00 1.15367235E-02 5.86095947E-02 1.62981888E+02 57 | S-TIM39 1.47008105E+00 2.24752746E-01 2.44968592E+00 1.02900432E-02 5.41276904E-02 2.37434940E+02 58 | S-TIH 1 1.60326759E+00 2.42980935E-01 1.81313592E+00 1.18019139E-02 5.91363658E-02 1.61218747E+02 59 | S-TIH 3 1.64797648E+00 2.67261917E-01 2.19772845E+00 1.21917693E-02 5.97893039E-02 1.92158340E+02 60 | S-TIH 4 1.66755531E+00 2.94411865E-01 2.49422119E+00 1.22052137E-02 5.97775329E-02 2.14869618E+02 61 | S-TIH 6 1.77227611E+00 3.45691250E-01 2.40788501E+00 1.31182633E-02 6.14479619E-02 2.00753254E+02 62 | S-TIH10 1.61549392E+00 2.62433239E-01 2.09426189E+00 1.19830897E-02 5.96510240E-02 1.81657554E+02 63 | S-TIH11 1.72677471E+00 3.24568628E-01 2.65816809E+00 1.29369958E-02 6.18255245E-02 2.21904637E+02 64 | S-TIH13 1.62224674E+00 2.93844589E-01 1.99225164E+00 1.18368386E-02 5.90208025E-02 1.71959976E+02 65 | S-TIH14 1.68915108E+00 2.90462024E-01 2.37971516E+00 1.28202514E-02 6.18090841E-02 2.01094352E+02 66 | S-TIH18 1.59921608E+00 2.59532164E-01 2.12454543E+00 1.16469304E-02 5.84824883E-02 1.86927779E+02 67 | S-TIH23 1.73986485E+00 3.13894918E-01 2.31093206E+00 1.29441300E-02 6.12116868E-02 1.97420482E+02 68 | S-TIH53 1.87904886E+00 3.69719775E-01 2.33730863E+00 1.44121770E-02 6.38817990E-02 1.82668180E+02 69 | S-LAL 7 9.16121247E-01 7.65948319E-01 1.27745023E+00 3.95889743E-03 1.67547425E-02 1.10762706E+02 70 | S-LAL 8 1.30663291E+00 5.71377253E-01 1.24303605E+00 6.11862448E-03 2.12721470E-02 9.06285686E+01 71 | S-LAL 9 1.16195687E+00 6.44860099E-01 1.25062221E+00 1.59659509E-02 5.05502467E-04 9.38284169E+01 72 | S-LAL10 1.52812575E+00 3.67965267E-01 1.11751784E+00 7.76817644E-03 2.72026548E-02 8.88697400E+01 73 | S-LAL12 9.92053895E-01 7.71377731E-01 1.18296264E+00 1.67095063E-02 2.36750156E-03 1.05901080E+02 74 | S-LAL13 9.80071267E-01 8.32904776E-01 1.28111995E+00 3.89123698E-03 1.89164592E-02 9.89052676E+01 75 | S-LAL14 1.23720970E+00 5.89722623E-01 1.31921880E+00 1.53551320E-02 -3.07896250E-04 9.37202947E+01 76 | S-LAL18 1.50276318E+00 4.30224497E-01 1.34726060E+00 1.45462356E-02 -3.32784153E-03 9.33508342E+01 77 | S-LAL54 1.41910189E+00 2.58416881E-01 1.07385537E+00 7.26647428E-03 2.63842499E-02 1.02555463E+02 78 | S-LAL56 1.54052000E+00 2.17748704E-01 1.30456122E+00 8.26765101E-03 3.28533726E-02 1.24527479E+02 79 | S-LAL58 1.06368789E+00 7.44939067E-01 1.59178942E+00 1.85199640E-02 1.16295862E-03 1.56636025E+02 80 | S-LAL59 1.13962742E+00 8.05227838E-01 1.29488061E+00 4.93294862E-03 2.02479960E-02 9.34746507E+01 81 | S-LAL61 1.11073292E+00 8.59347773E-01 1.26707433E+00 4.64181248E-03 1.92989261E-02 8.73917698E+01 82 | S-LAM 2 1.77130000E+00 1.95814230E-01 1.19487834E+00 9.76652444E-03 4.12718628E-02 1.10458122E+02 83 | S-LAM 3 1.64258713E+00 2.39634610E-01 1.22483026E+00 8.68246020E-03 3.51226242E-02 1.16604369E+02 84 | S-LAM 7 1.71014712E+00 2.56943292E-01 1.63986271E+00 1.05161080E-02 5.02809636E-02 1.46181217E+02 85 | S-LAM51 1.63847200E+00 1.88330533E-01 1.47502357E+00 9.04853452E-03 3.72740173E-02 1.37770050E+02 86 | S-LAM52 1.73442942E+00 1.51553910E-01 1.46225433E+00 1.00690928E-02 4.70634701E-02 1.40084396E+02 87 | S-LAM54 1.84213306E+00 1.75468631E-01 1.25750878E+00 9.43993220E-03 3.95281122E-02 8.65463013E+01 88 | S-LAM55 1.85412979E+00 1.65450323E-01 1.27255422E+00 1.08438152E-02 5.14050980E-02 1.09986837E+02 89 | S-LAM58 1.70984856E+00 1.73342897E-01 1.64833565E+00 1.00852127E-02 4.70890831E-02 1.57468520E+02 90 | S-LAM59 1.63056133E+00 1.86994897E-01 1.30014289E+00 8.99690705E-03 3.68011993E-02 1.22239544E+02 91 | S-LAM60 1.60673056E+00 3.66415640E-01 1.31761804E+00 7.75046140E-03 2.89967611E-02 9.30720709E+01 92 | S-LAM61 1.73883330E+00 1.50937430E-01 1.12118445E+00 9.80244105E-03 4.33179685E-02 1.01214625E+02 93 | S-LAM66 1.92094221E+00 2.19901208E-01 1.72705231E+00 1.15075241E-02 5.47993543E-02 1.20133674E+02 94 | S-LAH51 1.82586991E+00 2.83023349E-01 1.35964319E+00 9.35297152E-03 3.73803057E-02 1.00655798E+02 95 | S-LAH52 1.85390925E+00 2.97925555E-01 1.39382086E+00 9.55320687E-03 3.93816850E-02 1.02706848E+02 96 | S-LAH53 1.91811619E+00 2.53724399E-01 1.39473885E+00 1.02147684E-02 4.33176011E-02 1.01938021E+02 97 | S-LAH55 1.95615766E+00 3.19216215E-01 1.39173189E+00 9.79338965E-03 3.76836296E-02 9.48775271E+01 98 | S-LAH58 1.78764964E+00 6.52635600E-01 1.79914564E+00 8.47378536E-03 3.13126408E-02 1.32788001E+02 99 | S-LAH59 1.51372967E+00 7.02462343E-01 1.33600982E+00 7.05246901E-03 2.49488689E-02 1.00085908E+02 100 | S-LAH60 1.95243469E+00 3.07100210E-01 1.56578094E+00 1.06442437E-02 4.56735302E-02 1.10281410E+02 101 | S-LAH63 1.89458276E+00 2.68702978E-01 1.45705526E+00 1.02277048E-02 4.42801243E-02 1.04874927E+02 102 | S-LAH64 1.83021453E+00 2.91563590E-01 1.28544024E+00 9.04823290E-03 3.30756689E-02 8.93675501E+01 103 | S-LAH65 1.68191258E+00 4.93779818E-01 1.45682822E+00 7.76684250E-03 2.88916181E-02 9.92574356E+01 104 | S-LAH66 1.39280586E+00 6.79577094E-01 1.38702069E+00 6.08475118E-03 2.33925351E-02 9.58354094E+01 105 | S-LAH79 2.32557148E+00 5.07967133E-01 2.43087198E+00 1.32895208E-02 5.28335449E-02 1.61122408E+02 106 | S-YGH51 1.08280170E+00 9.33988681E-01 1.32367286E+00 1.81156360E-02 3.04157575E-03 9.10353195E+01 107 | S-FTM16 1.32940907E+00 1.41512125E-01 1.44299068E+00 1.02377287E-02 5.78081956E-02 1.50597139E+02 108 | S-NBM51 1.37023101E+00 1.77665568E-01 1.30515471E+00 8.71920342E-03 4.05725552E-02 1.12703058E+02 109 | S-NBH 5 1.47544521E+00 1.93060095E-01 1.50939010E+00 9.55836740E-03 4.60430483E-02 1.26422746E+02 110 | S-NBH 8 1.61344136E+00 2.57295888E-01 1.98364455E+00 1.06386752E-02 4.87071624E-02 1.59784404E+02 111 | S-NBH51 1.71203689E+00 2.55989588E-01 1.81456998E+00 1.07724134E-02 4.88593504E-02 1.36359013E+02 112 | S-NPH 1 1.75156623E+00 3.64006304E-01 2.47874141E+00 1.35004681E-02 6.68245147E-02 1.70756006E+02 113 | S-NPH 2 2.03869510E+00 4.37269641E-01 2.96711461E+00 1.70796224E-02 7.49254813E-02 1.74155354E+02 114 | -------------------------------------------------------------------------------- /Eye.m: -------------------------------------------------------------------------------- 1 | classdef Eye < Bench 2 | % EYE implements human eye optics 3 | % The human eye model is described in Escudero-Sanz & Navarro, 4 | % "Off-axis aberrations of a wide-angle schematic eye model", 5 | % JOSA A, 16(8), 1881-1891 (1999). Also, Dubbelman M, Van der 6 | % Heijde GL. "The shape of the aging human lens: curvature, 7 | % equivalent refractive index and the lens paradox." Vision Res. 8 | % 2001;41:1867?1877 and A. V. Goncharov and C. Dainty. Wide-field 9 | % schematic eye models with gradient-index lens. J Opt Soc Am A 10 | % Opt Image Sci Vis, 24(8):2157?74, 2007. were used. 11 | % 12 | % Following G. K. Von Noorden and E. C. Campos. "Binocular 13 | % vision and ocular motility: theory and management of strabismus." 14 | % Gunter K. 6th ed. St. Louis: CV Mosby, 2002, the eye center of 15 | % rotation was taken to be 13.3 mm behind the corneal apex, which 16 | % puts it 1.34 mm behind the center of the eye (total eye depth is 17 | % 23.93 here). High precision is immaterial since eye center 18 | % of rotation actually moves by as much as 1 mm as the eye rotates, 19 | % so the idea of the eye center of rotation is just an approximation. 20 | % The retina shape was taken to be an oblate spheroid according to 21 | % D. A. Atchison et al. "Shape of the retinal surface in emmetropia 22 | % and myopia". Invest Ophthalmol Vis Sci, 46(8):2698?707, Aug 2005. 23 | % The retinal spheroid and lens slants were ignored here. 24 | % 25 | % Lens accomodation is modeled by its diameter variation assuming 26 | % that the lens volume is constant for all diameters. This assumption 27 | % was based on Hermans et al., "Constant Volume of the Human Lens and 28 | % Decrease in Surface Area of the Capsular Bag during Accommodation: 29 | % An MRI and Scheimp?ug Study", Investigative Ophthalmology & Visual 30 | % Science 50(1), 281-289 (2009). 31 | % 32 | % The back lens surface was modeled by a paraboloid of revolution 33 | % x = 1/(2 R) ( y^2 + z^2 ), its (constant) volume V is 34 | % given by pi/8 D^2 h, where h is the paraboloid height. Hence, 35 | % h(D) = 8 V / (pi D^2) and R(D) = pi D^4 / (64 V). The front lens 36 | % surface was modeled by a hyperboloid of revolution given by 37 | % x = R/(1+k) (1 - sqrt( 1 - a( y^2 + z^2)/R^2 ) ). The volume 38 | % is pi/8 D^2 h (1 - h/(6 R/(1+k) + 3h) ), where h is the height of 39 | % of the hyperboloid from it apex to the cut of diameter D. 40 | % Corresponding h(D) and R(D) formulas were obtained by solving a cubic 41 | % equation, and its closed-from solution is implemented here. 42 | % 43 | % Member functions: 44 | % 45 | % e = Eye( lens_diameter, pupil_diameter ) - constructor function 46 | % INPUT: 47 | % lens_diameter - eye lens diameter, mm, specifies the eye's 48 | % accomodative state, defaults to no accommodation 49 | % pupil_diameter - eye pupil diameter, mm, defaults to 3 50 | % OUTPUT: 51 | % e - eye object 52 | % 53 | % e = e.Lens( lens_diameter ) - modify the eye's accommodative state 54 | % INPUT: 55 | % lens_diameter = new lens diameter, mm 56 | % OUTPUT: 57 | % e - eye object with the lens diameter modified 58 | % 59 | % V = e.eye_lens_vol( lens_diameter ) - returns the eye lens's volume 60 | % INPUT: 61 | % lens_diameter - eye lens diameter, mm 62 | % OUTPUT: 63 | % V - volume of the lens, mm^3 64 | % 65 | % e.rotate( rot_axis, rot_angle ) - rotate the eye e with all its elements 66 | % INPUT: 67 | % rot_axis - 1x3 vector defining the rotation axis 68 | % rot_angle - rotation angle (radians) 69 | % 70 | % e.translate( tr_vec ) - translate the eye e with all its elements 71 | % INPUT: 72 | % tr_vec - 1x3 translation vector 73 | % 74 | % e.display() - displays the eye e information 75 | % 76 | % e.draw() - draws the eye e in the current axes 77 | % 78 | % Copyright: Yury Petrov, 2016 79 | % 80 | 81 | properties ( Constant ) 82 | % All distances wrt the eye center of rotation 83 | % cornea 84 | Cornea1D = 11 % front surface diameter, mm 85 | Cornea1R = 7.76 % front surface radius of curvature, mm (30-year old, Goncharov et al. 2009) 86 | Cornea1k = -0.10 % front conic constant (30-year old, Goncharov et al. 2009, Escudero-Sanz et al. 1999 uses -0.26 87 | Cornea1x = -13.3 % surface position wrt eye center of rotation 88 | Cornea2D = 10.5 % back surface diameter 89 | Cornea2R = 6.52 % back surface radius of curvature (30-year old, Goncharov et al. 2009), 6.50 (Escudero-Sanz et al. 1999) 90 | Cornea2k = -0.30 % back conic constant (30-year old, Goncharov et al. 2009), 0 (Escudero-Sanz et al. 1999) 91 | Cornea2x = -12.75 % back surface position, cornea thickness is .55 (both Goncharov and Escudero-Sanz) 92 | % pupil 93 | PupilDout = 11 % outer pupil diameter 94 | Pupilx = -10.25 % -9.85 % Aqueous thickness is 3.05 (Goncharov, 2009 30-year old and also Escudero-Sanz & Navarro). 95 | % I shifted pupil plane .15 mm anterior to 96 | % prevent front lens surface getting into the 97 | % pupil surface for the smallest pupil (2mm) and 98 | % the strongest accommodation (7D) 99 | % lens 100 | Lens1k = -3.1316 % strongly hyperbolic 101 | Lens2k = -1.0 % parabolic surface 102 | LensMx = -8.55 % x position of the lens hyp|par boundary (calculated from Goncharov et al, 2009) 103 | LensV1 = 53.49 % constant volume of the lens hyperbolic part 104 | LensV2 = 106.66 % constant volume of the lens parabolic part 105 | % retina 106 | RetinaR = -12.94 % radius of curvature at the apex or 1/2 retina length along optic axis 107 | Retinak = 0.275 % emmetropic retina is oblong 108 | Retinax = 10.6203 % retina apex position, this puts equatorial plane at .47 109 | % RetinaRotz = 11; % rotated around vertical axis away from the nose, deg 110 | end 111 | 112 | properties % variable depending on accommodation 113 | PupilD = 3 % 3 mm pupil diameter by default 114 | LensD = 10.6318 % value for 0D accommodation 115 | % as measured by Hermans et al. (1990) and focus of 2 micrometers 116 | Lens1x = -9.70 % depends on accommodation 117 | Lens1R = 11.51 % front lens 0D radius (30-year old Goncharov 2009), 11.25 (Escudero-Sanz et al. 1999) 118 | Lens2x = -5.70 % depends on accommodation 119 | Lens2R = -6.01 % back lens 0D radius (30-year old Goncharov 2009), -5.70 (Escudero-Sanz et al. 1999) 120 | image = []; 121 | end 122 | 123 | methods 124 | 125 | function self = Lens( self, lens_diameter ) 126 | % Create eye lens 127 | self.LensD = lens_diameter; 128 | D = lens_diameter; 129 | 130 | % hyperbolic front surface 131 | V = self.LensV1; 132 | k = self.Lens1k; 133 | a = 1 + k; 134 | A = 5 * D^6 * pi^2 - 1024 * a * V^2; 135 | d = a^3 * D^6 * ( 5 * D^12 * pi^4 + 2112 * a * D^6 * pi^2 * V^2 + 1572864 * a^2 * V^4 ); 136 | B = ( -360 * a^2 * D^6 * pi^2 * V - 32768 * a^3 * V^3 + 5 * pi * sqrt( d ) )^(1/3); 137 | C = 10 * pi * D^2; 138 | % below are the 3 real roots of the cubic equation for the surface height, the 3d root gives the height 139 | %h1 = ( 32 * V + A / B - B / a ) / C; 140 | %h2 = ( 64 * V + ( 1 + 1i * sqrt(3) ) * (-A) / B + ( 1 - 1i * sqrt(3) ) * B / a ) / ( 2 * C ); 141 | h3 = ( 64 * V + 1i * ( 1i + sqrt(3) ) * A / B + ( 1 + 1i * sqrt(3) ) * B / a ) / ( 2 * C ); 142 | hf = real( h3 ); % remove zero imaginary part 143 | self.Lens1R = a * hf / 2 + D^2 / ( 8 * hf ); 144 | 145 | % parabolic back surface 146 | V = self.LensV2; 147 | self.Lens2R = -pi * D^4 / ( 64 * V ); 148 | hb = 8 * V / ( pi * D^2 ); 149 | 150 | % update the lens eye element shape if the lens already exists 151 | if self.cnt > 3 152 | % find the current lens position (center of the equatorial circle) 153 | lv = self.elem{ 5 }.r - self.elem{ 4 }.r; % vector from the lens front to the lens back 154 | lc = self.elem{ 4 }.r + ( self.LensMx - self.Lens1x ) * lv / norm( lv ); 155 | 156 | % create, orient, and position the new front surface 157 | lens = Lens( [ -hf 0 0 ], self.LensD, self.Lens1R, self.Lens1k, { 'aqueous' 'lens' } ); 158 | lens.rotate( self.elem{ 4 }.rotax, self.elem{ 4 }.rotang ); % rotate the surface normal 159 | lens.r = rodrigues_rot( lens.r, self.elem{ 4 }.rotax, self.elem{ 4 }.rotang ); % rotate the surface position 160 | lens.r = lens.r + lc ; 161 | self.elem{ 4 } = lens; 162 | 163 | % create, orient, and position the new back surface 164 | lens = Lens( [ hb 0 0 ], self.LensD, self.Lens2R, self.Lens2k, { 'lens' 'vitreous' } ); 165 | lens.rotate( self.elem{ 5 }.rotax, self.elem{ 5 }.rotang ); 166 | lens.r = rodrigues_rot( lens.r, self.elem{ 4 }.rotax, self.elem{ 4 }.rotang ); 167 | lens.r = lens.r + lc ; 168 | self.elem{ 5 } = lens; 169 | end 170 | self.Lens1x = self.LensMx - hf; % front surface apex x 171 | self.Lens2x = self.LensMx + hb; % back surface apex x 172 | end 173 | 174 | function self = Eye( lens_diameter, pupil_diameter ) 175 | % Eye constructor function 176 | if nargin == 0 || ( nargin > 0 && isempty( lens_diameter ) ) 177 | lens_diameter = self.LensD; % 0D accommodation by default 178 | end 179 | if nargin > 1 180 | self.PupilD = pupil_diameter; 181 | end 182 | 183 | % compound eye elements 184 | 185 | % cornea 186 | cornea1 = Lens( [ self.Cornea1x 0 0 ], self.Cornea1D, self.Cornea1R, self.Cornea1k, { 'air' 'cornea' } ); 187 | self.cnt = self.cnt + 1; 188 | self.elem{ self.cnt } = cornea1; 189 | cornea2 = Lens( [ self.Cornea2x 0 0 ], self.Cornea2D, self.Cornea2R, self.Cornea2k, { 'cornea' 'aqueous' } ); 190 | self.cnt = self.cnt + 1; 191 | self.elem{ self.cnt } = cornea2; 192 | 193 | % pupil 194 | % pupil = Aperture( [ self.Pupilx 0 0 ], [ self.PupilD, self.PupilDout, 1000 ] ); % the third diameter is the one actually used for tracing, the second just to draw 195 | pupil = Lens( [ self.Pupilx 0 0 ], [ self.PupilD, -1.77*self.RetinaR ], -self.RetinaR, self.Retinak, { 'cornea' 'soot' } ); % pupil extends around the retina to simulate the opaque choroid 196 | self.cnt = self.cnt + 1; 197 | self.elem{ self.cnt } = pupil; 198 | 199 | % lens 200 | % caluclate lens dimensions from its diameter assuming constant volume 201 | self.Lens( lens_diameter ); 202 | 203 | lens1 = Lens( [ self.Lens1x 0 0 ], self.LensD, self.Lens1R, self.Lens1k, { 'aqueous' 'lens' } ); 204 | self.cnt = self.cnt + 1; 205 | self.elem{ self.cnt } = lens1; 206 | lens2 = Lens( [ self.Lens2x 0 0 ], self.LensD, self.Lens2R, self.Lens2k, { 'lens' 'vitreous' } ); 207 | self.cnt = self.cnt + 1; 208 | self.elem{ self.cnt } = lens2; 209 | 210 | % retina 211 | retina = Retina( [ self.Retinax 0 0 ], self.RetinaR, self.Retinak ); 212 | % retina.rotate( [ 0 0 1 ], self.RetinaRotz * pi / 180 ); 213 | self.cnt = self.cnt + 1; 214 | self.elem{ self.cnt } = retina; 215 | self.image = retina.image; 216 | end 217 | 218 | function V = eye_lens_vol( self, D ) 219 | % calculate eye lens volume given its surface parameters at 0D, this 220 | % is a utility function to find D such that total V = 160 mm^2 221 | x = self.Lens1R / ( 1 + self.Lens1k ); 222 | h = x + sqrt( x^2 - D^2 / ( 4 * ( 1 + self.Lens1k ) ) ); 223 | V1 = pi * D^2 / 8 * h * ( 1 - h / ( 6 * self.Lens1R / ( 1 + self.Lens1k ) + 3 * h ) ); 224 | h = abs( D^2 /( 8 * self.Lens2R ) ); 225 | V2 = pi / 8 * D^2 * h; % parabolic volume 226 | V = V1 + V2; 227 | end 228 | 229 | end 230 | 231 | end 232 | 233 | -------------------------------------------------------------------------------- /hatchfill.m: -------------------------------------------------------------------------------- 1 | function H = hatchfill(A,STYL,ANGLE,SPACING,FACECOL,LINECOL) 2 | %% HATCHFILL Hatching and speckling of patch objects 3 | %% HATCHFILL(A) fills the patch(es) with handle(s) A. 4 | %% A can be a vector of handles or a single handle. 5 | %% If A is a vector, then all objects of A should 6 | %% be part of the same group for predictable results. 7 | %% The hatch consists of black lines angled at 8 | %% 45 degrees spaced 5 pixels apart, with no color 9 | %% filling between the lines. 10 | %% 11 | %% HATCHFILL(A,STYL) applies STYL pattern with default paramters. 12 | %% - STYL can be 'single' for single lines (the default), 13 | %% 'cross' for a double-crossed hatch, 'speckle' for 14 | %% speckling inside the patch boundary, and 'outspeckle' for 15 | %% for speckling outside the boundary. 'fill' will 16 | %% apply only a gray fill and no hatching. 17 | %% 18 | %% HATCHFILL(A,STYL,ANGLE,SPACING) applies a hatch/speckle with 19 | %% customized parameters: 20 | %% - ANGLE sets the angle of hatch lines. For speckling, it 21 | %% controls the width of the speckling region. 22 | %% - SPACING controls the spacing of hatch lines or the 23 | %% density of speckle points. 24 | %% If STYL is 'fill', then ANGLE and SPACING are ignored. 25 | %% 26 | %% HATCHFILL(A,STYL,ANGLE,SPACING,FACECOL) allows the user 27 | %% to specify a fill color. (The default is 'none'.) 28 | %% 29 | %% H = HATCHFILL(...) returns handles to the line objects 30 | %% comprising the hatch/speckle. 31 | %% 32 | %% Examples: 33 | %% Gray region with hatching: 34 | %% hh = hatchfill(a,'cross',45,5,[0.5 0.5 0.5]); 35 | %% 36 | %% Speckled region: 37 | %% hatchfill(a,'speckle',7,1); 38 | %% 39 | %% NOTE: This function depends on the script hatch_xy.m 40 | %% based on the work of R. Pawlowicz, K. Pankratov, and 41 | %% Iram Weinstein. 42 | %% 43 | %% Neil Tandon 11 Jul 2011 44 | %% 45 | %% Yury Petrov added LINECOL argument 46 | 47 | % set defaults: 48 | if nargin == 1 49 | STYL = 'single'; 50 | ANGLE = 45; 51 | SPACING = 5; 52 | FACECOL = 'none'; 53 | LINECOL = [ 0 0 0 ]; 54 | end 55 | 56 | % For backwards compatability: 57 | if strcmpi(STYL,'none') 58 | STYL = 'fill'; 59 | end 60 | 61 | if nargin == 2 62 | if strcmpi(STYL,'single') || strcmpi(STYL,'cross') 63 | ANGLE = 45; 64 | SPACING = 5; 65 | FACECOL = 'none'; 66 | elseif strcmpi(STYL,'speckle') || strcmpi(STYL,'outspeckle') 67 | ANGLE = 7; 68 | SPACING = 1; 69 | FACECOL = 'none'; 70 | elseif strcmpi(STYL,'fill') 71 | FACECOL = [0.8 0.8 0.8]; 72 | end 73 | LINECOL = [ 0 0 0 ]; 74 | end 75 | 76 | if nargin == 3 77 | error('Invalid number of input arguments'); 78 | end 79 | 80 | if nargin == 4 81 | if strcmpi(STYL,'fill') 82 | FACECOL = [0.8 0.8 0.8]; 83 | else 84 | FACECOL = 'none'; 85 | end 86 | LINECOL = [ 0 0 0 ]; 87 | end 88 | 89 | if nargin == 5 90 | LINECOL = [ 0 0 0 ]; 91 | end 92 | 93 | if ( ~strcmpi(STYL,'single') && ~strcmpi(STYL,'cross') && ... 94 | ~strcmpi(STYL,'speckle') && ~strcmpi(STYL,'outspeckle') && ... 95 | ~strcmpi(STYL,'fill') ) 96 | error(['Invalid style: ',STYL]) 97 | end 98 | 99 | linec = LINECOL; 100 | linew = 0.5; 101 | specksize = 2; 102 | 103 | % axis handle is one or two hierarchical levels up: 104 | % (Additional check suggested by Dan K) 105 | hax = get(A(1),'parent'); 106 | is_axes = strcmpi(get(hax,'type'),'axes'); 107 | if ~is_axes 108 | hax = get(hax,'parent'); 109 | end 110 | is_axes = strcmpi(get(hax,'type'),'axes'); 111 | 112 | x_is_log = 0; y_is_log = 0; 113 | x_is_reverse = 0; y_is_reverse = 0; 114 | 115 | if is_axes 116 | axsize_in = get(hax,'position'); 117 | y_is_log = strcmpi(get(hax,'yscale'),'log'); 118 | if y_is_log 119 | ylims = get(hax,'ylim'); 120 | dy = (ylims(2) - ylims(1))/(log10(ylims(2))-log10(ylims(1))); 121 | set(hax,'units','pixels'); 122 | axsize = get(hax,'position'); 123 | set(hax,'position',[ axsize(1:3) dy*axsize(4) ]); 124 | set(hax,'units','normalized') 125 | end 126 | 127 | x_is_log = strcmpi(get(hax,'xscale'),'log'); 128 | if x_is_log 129 | xlims = get(hax,'xlim'); 130 | dx = (xlims(2) - xlims(1))/(log10(xlims(2))-log10(xlims(1))); 131 | set(hax,'units','pixels'); 132 | axsize = get(hax,'position'); 133 | set(hax,'position',[ axsize(1:2) dx*axsize(3) axsize(4) ]); 134 | set(hax,'units','normalized') 135 | end 136 | 137 | if strcmp(STYL,'single') || strcmp(STYL,'cross') 138 | y_is_reverse = strcmpi(get(hax,'ydir'),'reverse'); 139 | if y_is_reverse 140 | ANGLE = -ANGLE; 141 | end 142 | x_is_reverse = strcmpi(get(hax,'xdir'),'reverse'); 143 | if x_is_reverse 144 | ANGLE = 180-ANGLE; 145 | end 146 | end 147 | end 148 | 149 | % Apply hatch: 150 | j = 1; 151 | for k = 1:length(A) 152 | set(A,'facecolor',FACECOL); 153 | v = get(A(k),'vertices'); 154 | if any(v(end,:)~=v(1,:)) 155 | v(end+1,:) = v(1,:); 156 | end 157 | x = v(:,1); 158 | if x_is_log 159 | x = log10(v(:,1)); 160 | end 161 | y = v(:,2); 162 | if y_is_log 163 | y = log10(v(:,2)); 164 | end 165 | 166 | if strcmp(STYL,'fill') 167 | H = NaN; 168 | continue 169 | end 170 | 171 | [xhatch,yhatch] = hatch_xy(x,y,STYL,ANGLE,SPACING); 172 | if x_is_log 173 | xhatch = 10.^xhatch; 174 | end 175 | if y_is_log 176 | yhatch = 10.^yhatch; 177 | end 178 | if strcmp(STYL,'speckle') || strcmp(STYL,'outspeckle') 179 | if any(xhatch) 180 | H(j) = line(xhatch,yhatch,'marker','.','linest','none', ... 181 | 'markersize',specksize,'color',linec); 182 | j = j+1; 183 | end 184 | elseif strcmp(STYL,'single') || strcmp(STYL,'cross') 185 | H(j) = line(xhatch,yhatch); 186 | set(H(j),'color',linec,'linewidth',linew); 187 | j = j+1; 188 | end 189 | end 190 | 191 | if y_is_log || x_is_log 192 | set(hax,'position',axsize_in); 193 | end 194 | 195 | 196 | 197 | 198 | 199 | %%%%%%%%%%%%%%%%%%% SUBFUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%% 200 | 201 | function [xi,yi,x,y]=hatch_xy(x,y,varargin); 202 | % 203 | % M_HATCH Draws hatched or speckled interiors to a patch 204 | % 205 | % M_HATCH(LON,LAT,STYL,ANGLE,STEP,...line parameters); 206 | % 207 | % INPUTS: 208 | % X,Y - vectors of points. 209 | % STYL - style of fill 210 | % ANGLE,STEP - parameters for style 211 | % 212 | % E.g. 213 | % 214 | % 'single',45,5 - single cross-hatch, 45 degrees, 5 points apart 215 | % 'cross',40,6 - double cross-hatch at 40 and 90+40, 6 points apart 216 | % 'speckle',7,1 - speckled (inside) boundary of width 7 points, density 1 217 | % (density >0, .1 dense 1 OK, 5 sparse) 218 | % 'outspeckle',7,1 - speckled (outside) boundary of width 7 points, density 1 219 | % (density >0, .1 dense 1 OK, 5 sparse) 220 | % 221 | % 222 | % H=M_HATCH(...) returns handles to hatches/speckles. 223 | % 224 | % [XI,YI,X,Y]=MHATCH(...) does not draw lines - instead it returns 225 | % vectors XI,YI of the hatch/speckle info, and X,Y of the original 226 | % outline modified so the first point==last point (if necessary). 227 | % 228 | % Note that inside and outside speckling are done quite differently 229 | % and 'outside' speckling on large coastlines can be very slow. 230 | 231 | % 232 | % Hatch Algorithm originally by K. Pankratov, with a bit stolen from 233 | % Iram Weinsteins 'fancification'. Speckle modifications by R. Pawlowicz. 234 | % 235 | % R Pawlowicz 15/Dec/2005 236 | 237 | styl='speckle'; 238 | angle=7; 239 | step=1/2; 240 | 241 | if length(varargin)>0 & isstr(varargin{1}), 242 | styl=varargin{1}; 243 | varargin(1)=[]; 244 | end; 245 | if length(varargin)>0 & ~isstr(varargin{1}), 246 | angle=varargin{1}; 247 | varargin(1)=[]; 248 | end; 249 | if length(varargin)>0 & ~isstr(varargin{1}), 250 | step=varargin{1}; 251 | varargin(1)=[]; 252 | end; 253 | 254 | I = zeros(1,length(x)); 255 | %[x,y,I]=m_ll2xy(lon,lat,'clip','patch'); 256 | 257 | 258 | if x(end)~=x(1) & y(end)~=y(1), 259 | x=x([1:end 1]); 260 | y=y([1:end 1]); 261 | I=I([1:end 1]); 262 | end; 263 | 264 | if strcmp(styl,'speckle') | strcmp(styl,'outspeckle'), 265 | angle=angle*(1-I); 266 | end; 267 | 268 | if size(x,1)~=1, 269 | x=x(:)'; 270 | angle=angle(:)'; 271 | end; 272 | if size(y,1)~=1, 273 | y=y(:)'; 274 | end; 275 | 276 | 277 | % Code stolen from Weinstein hatch 278 | oldu = get(gca,'units'); 279 | set(gca,'units','points'); 280 | sza = get(gca,'pos'); sza = sza(3:4); 281 | set(gca,'units',oldu) % Set axes units back 282 | 283 | xlim = get(gca,'xlim'); 284 | ylim = get(gca,'ylim'); 285 | xsc = sza(1)/(xlim(2)-xlim(1)+eps); 286 | ysc = sza(2)/(ylim(2)-ylim(1)+eps); 287 | 288 | switch lower(styl), 289 | case 'single', 290 | [xi,yi]=drawhatch(x,y,angle,step,xsc,ysc,0); 291 | if nargout<2, 292 | xi=line(xi,yi,varargin{:}); 293 | end; 294 | case 'cross', 295 | [xi,yi]=drawhatch(x,y,angle,step,xsc,ysc,0); 296 | [xi2,yi2]=drawhatch(x,y,angle+90,step,xsc,ysc,0); 297 | xi=[xi,xi2]; 298 | yi=[yi,yi2]; 299 | if nargout<2, 300 | xi=line(xi,yi,varargin{:}); 301 | end; 302 | case 'speckle', 303 | [xi,yi ] =drawhatch(x,y,45, step,xsc,ysc,angle); 304 | [xi2,yi2 ]=drawhatch(x,y,45+90,step,xsc,ysc,angle); 305 | xi=[xi,xi2]; 306 | yi=[yi,yi2]; 307 | if nargout<2, 308 | if any(xi), 309 | xi=line(xi,yi,'marker','.','linest','none','markersize',2,varargin{:}); 310 | else 311 | xi=NaN; 312 | end; 313 | end; 314 | case 'outspeckle', 315 | [xi,yi ] =drawhatch(x,y,45, step,xsc,ysc,-angle); 316 | [xi2,yi2 ]=drawhatch(x,y,45+90,step,xsc,ysc,-angle); 317 | xi=[xi,xi2]; 318 | yi=[yi,yi2]; 319 | inside=logical(inpolygon(xi,yi,x,y)); % logical needed for v6! 320 | xi(inside)=[];yi(inside)=[]; 321 | if nargout<2, 322 | if any(xi), 323 | xi=line(xi,yi,'marker','.','linest','none','markersize',2,varargin{:}); 324 | else 325 | xi=NaN; 326 | end; 327 | end; 328 | 329 | end; 330 | 331 | 332 | return 333 | 334 | %%%%% 335 | 336 | function [xi,yi]=drawhatch(x,y,angle,step,xsc,ysc,speckle); 337 | % 338 | % This is the guts. 339 | % 340 | 341 | angle=angle*pi/180; 342 | 343 | % Idea here appears to be to rotate everthing so lines will be 344 | % horizontal, and scaled so we go in integer steps in 'y' with 345 | % 'points' being the units in x. 346 | % Center it for "good behavior". 347 | ca = cos(angle); sa = sin(angle); 348 | x0 = mean(x); y0 = mean(y); 349 | x = (x-x0)*xsc; y = (y-y0)*ysc; 350 | yi = x*ca+y*sa; % Rotation 351 | y = -x*sa+y*ca; 352 | x = yi; 353 | y = y/step; % Make steps equal to one 354 | 355 | % Compute the coordinates of the hatch line ............... 356 | yi = ceil(y); 357 | yd = [diff(yi) 0]; % when diff~=0 we are crossing an integer 358 | fnd = find(yd); % indices of crossings 359 | dm = max(abs(yd)); % max possible #of integers between points 360 | 361 | 362 | % 363 | % This is going to be pretty space-inefficient if the line segments 364 | % going in have very different lengths. We have one column per line 365 | % interval and one row per hatch line within that interval. 366 | % 367 | A = cumsum( repmat(sign(yd(fnd)),dm,1), 1); 368 | 369 | % Here we interpolate points along all the line segments at the 370 | % correct intervals. 371 | fnd1 = find(abs(A)<=abs( repmat(yd(fnd),dm,1) )); 372 | A = A+repmat(yi(fnd),dm,1)-(A>0); 373 | xy = (x(fnd+1)-x(fnd))./(y(fnd+1)-y(fnd)); 374 | xi = repmat(x(fnd),dm,1)+(A-repmat(y(fnd),dm,1) ).*repmat(xy,dm,1); 375 | yi = A(fnd1); 376 | xi = xi(fnd1); 377 | 378 | 379 | % Sorting points of the hatch line ........................ 380 | %%%yi0 = min(yi); yi1 = max(yi); 381 | % Sort them in raster order (i.e. by x, then by y) 382 | % Add '2' to make sure we don't have problems going from a max(xi) 383 | % to a min(xi) on the next line (yi incremented by one) 384 | xi0 = min(xi); xi1 = max(xi); 385 | ci = 2*yi*(xi1-xi0)+xi; 386 | [ci,num] = sort(ci); 387 | xi = xi(num); yi = yi(num); 388 | 389 | 390 | % if this happens an error has occurred somewhere (we have an odd 391 | % # of points), and the "fix" is not correct, but for speckling anyway 392 | % it really doesn't make a difference. 393 | if rem(length(xi),2)==1, 394 | disp('mhatch warning'); 395 | xi = [xi; xi(end)]; 396 | yi = [yi; yi(end)]; 397 | end 398 | 399 | % Organize to pairs and separate by NaN's ................ 400 | li = length(xi); 401 | xi = reshape(xi,2,li/2); 402 | yi = reshape(yi,2,li/2); 403 | 404 | % The speckly part - instead of taking the line we make a point some 405 | % random distance in. 406 | if length(speckle)>1 | speckle(1)~=0, 407 | 408 | if length(speckle)>1, 409 | % Now we get the speckle parameter for each line. 410 | 411 | % First, carry over the speckle parameter for the segment 412 | % yd=[0 speckle(1:end-1)]; 413 | yd=[speckle(1:end)]; 414 | A=repmat(yd(fnd),dm,1); 415 | speckle=A(fnd1); 416 | 417 | % Now give it the same preconditioning as for xi/yi 418 | speckle=speckle(num); 419 | if rem(length(speckle),2)==1, 420 | speckle = [speckle; speckle(end)]; 421 | end 422 | speckle=reshape(speckle,2,li/2); 423 | 424 | else 425 | speckle=[speckle;speckle]; 426 | end; 427 | 428 | % Thin out the points in narrow parts. 429 | % This keeps everything when abs(dxi)>2*speckle, and then makes 430 | % it increasingly sparse for smaller intervals. 431 | oldxi=xi;oldyi=yi; 432 | dxi=diff(xi); 433 | nottoosmall=sum(speckle,1)~=0 & rand(1,li/2)1, speckle=speckle(:,nottoosmall); end; 438 | % Now randomly scatter points (if there any left) 439 | li=length(dxi); 440 | if any(li), 441 | xi(1,:)=xi(1,:)+sign(dxi).*(1-rand(1,li).^0.5).*min(speckle(1,:),abs(dxi) ); 442 | xi(2,:)=xi(2,:)-sign(dxi).*(1-rand(1,li).^0.5).*min(speckle(2,:),abs(dxi) ); 443 | % Remove the 'zero' speckles 444 | if size(speckle,2)>1, 445 | xi=xi(speckle~=0); 446 | yi=yi(speckle~=0); 447 | end; 448 | end; 449 | 450 | else 451 | xi = [xi; ones(1,li/2)*nan]; % Separate the line segments 452 | yi = [yi; ones(1,li/2)*nan]; 453 | end; 454 | xi = xi(:)'; yi = yi(:)'; 455 | 456 | % Transform back to the original coordinate system 457 | yi = yi*step; 458 | xy = xi*ca-yi*sa; 459 | yi = xi*sa+yi*ca; 460 | xi = xy/xsc+x0; 461 | yi = yi/ysc+y0; 462 | -------------------------------------------------------------------------------- /draw_lens_engineering.m: -------------------------------------------------------------------------------- 1 | function [ f, pr ] = draw_lens_engineering( r, D, R, k, a, glass, name, varargin ) 2 | % 3 | % Make a pdf figure with the engineering drawing of a lens. Also exports 4 | % DXF file of its profile, if dxflib are installed in the MATLAB path. 5 | % 6 | % INPUT: 7 | % r - 1 x 2 vector of surface locations along the x-axis (optical axis) 8 | % D - 1 x 2 vector of the lens surface diameters 9 | % R - 1 x 2 vector of the lens surface radii of curvature 10 | % k - 1 x 2 vector of the lens conic constants 11 | % a - n x 2 vector of aspherical polynomial terms, one column per surface 12 | % glass - string with the lens material name 13 | % name - name of the drawing displayed as its title, defaults to current 14 | % date and time 15 | % varargin - strings specifying other manufacturing options. 'AR' adds 16 | % instructions to use broadband antireflective coating, 'flange' 17 | % followed by 2 numbers specifies a flange heights, other options 18 | % are up to you to specify. 19 | % 20 | % OUTPUT: 21 | % f - figure handle 22 | % pr - a cell array with high-precision profiles of the two lens surfaces 23 | % 24 | % EXAMPLE: 25 | % f = draw_lens_engineering( [ 0 12 ], [ 40 40 ], [ 50 -30 ], [ -2 -1 ], [], 'zeonex', [], 'AR', 'Make my lens really beautiful') 26 | % 27 | % Copyright: Yury Petrov, 2017 28 | % 29 | 30 | 31 | if nargin < 7 || isempty( name ) || strcmp( name, '' ) 32 | name = datestr( now, 'mmddyyyyHHMMSS' ); 33 | end 34 | if nargin < 6 35 | error( 'Please specify at least the first 6 arguments!' ); 36 | end 37 | if size( a, 2 ) > 2 38 | a = a'; 39 | if size( a, 2 ) ~= 2 40 | error( 'Matrix of polynomial coefficients has to be n x 2!' ); 41 | end 42 | end 43 | if sum( abs( a(:) ) ) == 0 44 | a = []; 45 | end 46 | 47 | % get screen size 48 | ScrSz = get( 0, 'ScreenSize' ); % desired aspect ratio 3:4 -> check and adjust target figure size 49 | if ScrSz(3) / ( 0.95 * ScrSz(4) ) > 4/3 %vertical space limits (FHD etc.) 50 | figh = 0.95 * ScrSz(4); %leave some space for taskbars, which could have varying sizes in different OS 51 | figw = figh * 4/3; 52 | else %horizontal space limits (could be in portrait mode) 53 | figw = ScrSz(3); 54 | figh = figw * 3/4; 55 | end 56 | f = figure( 'Name', name, 'Position', [ 0 0 figw figh ], 'Color', [ 1 1 1 ] ); 57 | 58 | % title 59 | an = annotation( 'textbox', [ 0.65 0.02 0.33 0.08 ], 'String', 'Title', 'FitBoxToText', 'off' ); 60 | an.FontSize = 18; 61 | an.Units = 'normalized'; 62 | an = text( 0.75, -0.06, name, 'FontSize', 36 ); 63 | an.Units = 'normalized'; 64 | axis off; 65 | 66 | % table with lens parameters 67 | t = uitable( f ); 68 | 69 | t.ColumnName = { '' 'S1' 'S2' }; 70 | t.ColumnWidth = { 200 120 120 }; 71 | t.ColumnFormat = { 'char' 'char' 'char' }; 72 | t.RowName = {}; 73 | t.RowStriping = 'off'; 74 | t.FontSize = 18; 75 | 76 | s1 = 'Convex'; 77 | if R(1) < 0 78 | s1 = 'Concave'; 79 | end 80 | s2 = 'Convex'; 81 | if R(2) > 0 82 | s2 = 'Concave'; 83 | end 84 | 85 | t.Data = { 86 | 'Shape', s1, s2; 87 | 'Radius', R(1), R(2); 88 | 'Aspherical conic', k(1), k(2); 89 | }; 90 | if ~isempty( a ) && ~sum( abs( a(:) ) ) == 0 91 | for i = 1 : size( a, 1 ) 92 | t.Data = [ t.Data; { [ 'Polynomial r^' num2str( i * 2 ) ], a( i, 1 ), a( i, 2 ); } ]; 93 | end 94 | end 95 | t.Data = [ t.Data; 96 | {'Clear aperture', D(1), D(2); 97 | 'Surface figure error', '6 fringes', '6 fringes'; 98 | 'Surface irregularity', '2 fringes', '2 fringes'; 99 | 'Surface roughness', '10 nm', '10 nm'; 100 | 'Scratch-dig', '40-20', '40-20' 101 | } 102 | ]; 103 | 104 | margin = 40; % margin for the table position, px 105 | parentPosition = get( f, 'position' ); 106 | t.Position( 3:4 ) = t.Extent( 3:4 ); 107 | %t.Position( 1:2 ) = [ parentPosition(1) + margin, parentPosition(2) + parentPosition(4) - t.Position(4) - margin ]; 108 | t.Position( 1:2 ) = [ parentPosition(1) + margin, parentPosition(4) - t.Position(4) - margin ]; 109 | 110 | % text with other specs 111 | Fr = {}; % empty cell to store Fresnel structures 112 | Frcnt = 0; % Fresnel surfaces counter 113 | str = { 114 | '1. All dimensions in millimeters', ... 115 | [ '2. Allowable optical material: ' glass], ... 116 | '3. Center element within 0.005 mm TIR', ... 117 | '4. Lens wedge: 0.02 mm ETD', ... 118 | '5. Fine ground on lens edge surface', ... 119 | '6. Lens fabrication via diamond turning' }; 120 | cnt = 6; 121 | flange = []; 122 | for i = 1 : length( varargin ) 123 | if isempty( varargin{ i } ) 124 | continue; 125 | end 126 | cnt = cnt + 1; 127 | if strcmpi( varargin{ i }, 'AR' ) 128 | str = [ str, ... 129 | [ num2str( cnt ) '. BBAR coating on both surfaces' ], ... 130 | ' Ravg < 1%', ... 131 | ' Spectrum range: 450 - 650 nm', ... 132 | ' Angle of incidence: 0 - 45 degrees' ]; 133 | elseif strcmpi( varargin{ i }, 'flange' ) 134 | flange = [ varargin{ i + 1 } varargin{ i + 2 } ]; 135 | if ~isnumeric( flange ) 136 | error( 'Two flange heights must be numeric values!' ); 137 | end 138 | varargin{ i + 1 } = []; % skip the next two values 139 | varargin{ i + 2 } = []; % skip the next two values 140 | str = [ str, [ num2str( cnt ) '. Flange height [' num2str( flange(1) ) ', ' num2str( flange(2) ) ']' ] ]; 141 | elseif strcmpi( varargin{ i }, 'Fresnel' ) 142 | Frcnt = Frcnt + 1; 143 | Fr{ Frcnt } = varargin{ i + 1 }; % read the Fresnel structure 144 | varargin{ i + 1 } = []; % skip the next value 145 | else 146 | str = [ str, [ num2str( cnt ) '. ' varargin{ i } ] ]; 147 | end 148 | end 149 | m = uicontrol( f, 'Style', 'text', 'String', str, 'Position', [ margin t.Position(2) - 450 400 400 ], 'BackgroundColor', [ 1 1 1 ], ... 150 | 'FontSize', 18, 'HorizontalAlignment', 'left' ); 151 | 152 | % draw the lens 153 | clr = [ 0 .3 .6 ]; % dark blue 154 | if isempty( Fr ) % both surfaces regular 155 | lw = 2; % line width 156 | else 157 | lw = 1; 158 | end 159 | 160 | % draw the lens front view (circles) 161 | a1 = axes( 'Position', [ 0.4 0.3 0.3 0.3 ], 'Clipping', 'off' ); 162 | if ~isempty( flange ) 163 | circle( [ D, D + flange ], [ r(1), r(2), 1.001 * r(1), 0.999 * r(2) ], clr, lw ); % make flange edge so slightly behind the front face edge and 164 | else 165 | circle( D, [ r(1), r(2) ], clr, lw ); 166 | end 167 | % in front of the back face edge to ensure right depth masking 168 | axis equal tight off; 169 | 170 | % draw the lens profile view 171 | a2 = axes( 'Position', [ 0.7 0.3 0.2 0.3 ], 'Clipping', 'off' ); 172 | npoints = 1000; 173 | if ~isempty( Fr ) > 0 && ~isempty( Fr{1} ) % a Fresnel structure 174 | npoints = 10000; 175 | % npoints = round( D(1) / 2 * 1000 ); % to get radius steps in microns 176 | end 177 | z1 = zeros( 1, npoints ); 178 | y1 = linspace( 0, D(1)/2, npoints ); % starting from the center 179 | npoints = 1000; 180 | if length( Fr ) > 1 && ~isempty( Fr{2} ) % a Fresnel structure 181 | npoints = 10000; 182 | % npoints = round( D(2) / 2 * 1000 ); % to get radius steps in microns 183 | end 184 | z2 = zeros( 1, npoints ); 185 | y2 = linspace( 0, D( 2 )/2, npoints ); % starting from the center 186 | if isempty( a ) 187 | if isempty( Fr ) % a regular conic lens 188 | p1 = asphlens( y1, z1, [ R( 1 ) R( 1 ) k( 1 ) ], 0 ); 189 | p2 = asphlens( y2, z2, [ R( 2 ) R( 2 ) k( 2 ) ], 0 ); 190 | else % a Fresnel lens 191 | if length( Fr ) == 1 192 | [ p1, y1 ] = frenlens( y1, Fr{1} ); 193 | p2 = asphlens( y2, z2, [ R( 2 ) R( 2 ) k( 2 ) ], 0 ); 194 | else 195 | if isempty( Fr{ 1 } ) % back surface Fresnel 196 | p1 = asphlens( y1, z1, [ R( 1 ) R( 1 ) k( 1 ) ], 0 ); 197 | [ p2, y2 ] = frenlens( y2, Fr{2} ); 198 | else % both surfaces Fresnel 199 | [ p1, y1 ] = frenlens( y1, Fr{1} ); 200 | [ p2, y2 ] = frenlens( y2, Fr{2} ); 201 | end 202 | end 203 | end 204 | else 205 | p1 = asphlens( y1, z1, [ R( 1 ) R( 1 ) k( 1 ) a( :, 1 )' ], 0 ); 206 | p2 = asphlens( y2, z2, [ R( 2 ) R( 2 ) k( 2 ) a( :, 2 )' ], 0 ); 207 | end 208 | if isempty( flange ) 209 | y = [ -fliplr( y1 ), y1, fliplr( y2 ), -y2, -y1( end ) ]; 210 | x = [ r( 1 ) + fliplr( p1 ), r( 1 ) + p1, r( 2 ) + fliplr( p2 ), r( 2 ) + p2, r( 1 ) + p1( end ) ]; 211 | else 212 | y = [ -fliplr( y1 ), y1, y1( end ) + flange(1), y2( end ) + flange(2), fliplr( y2 ), -y2, -y2( end ) - flange(2), -y1(end) - flange(1), -y1( end ) ]; 213 | x = [ r(1) + fliplr( p1 ), r(1) + p1, r(1) + p1( end ), r(2) + p2( end ), r(2) + fliplr( p2 ), r(2) + p2, r(2) + p2( end ), r(1) + p1( end ), r(1) + p1( end ) ]; 214 | end 215 | 216 | pr = { [ y1' p1' ] [ y2' p2' ] }; % radii and corresponding surface profiles 217 | 218 | p = patch( x, y, [ 1 0 0 ], 'EdgeColor', clr, 'LineWidth', lw ); 219 | hatchfill( p, 'single', 30, 35, 'none', clr ); 220 | axis equal tight off; 221 | 222 | if exist( 'dxf_open' ) % export dxf of the lens profile 223 | FID = dxf_open( [ name '_profile.dxf' ] ); 224 | dxf_polyline( FID, x', y', zeros( size( x ) )' ); 225 | dxf_close(FID); 226 | end 227 | 228 | % rads = y; 229 | % z = x; 230 | % nang = 100; 231 | % as = linspace( 0, 2 * pi, nang ); 232 | % [ angs, rads ] = meshgrid( as, rads ); 233 | % [ x, y ] = pol2cart( angs, rads ); 234 | % figure; 235 | % h = surf( x, y, repmat( z, nang, 1 )', 'LineStyle','none', 'FaceColor', 'interp', 'FaceAlpha', 0.5 ); axis vis3d equal; 236 | % if exist( 'stlwrite' ) % export dxf of the lens profile 237 | % fv = surf2patch( h, 'triangles' ); 238 | % stlwrite( [ name '.stl' ], fv ); 239 | % end 240 | 241 | % draw the dimensions 242 | R = max( D ) / 2; 243 | Rm = min( D ) / 2; 244 | an = annotation( 'line' ); 245 | set( an, 'Parent', a1 ); 246 | set( an, 'X', [ -R R ], 'Y', [ 0, 0 ], 'LineStyle', '-.', 'LineWidth', 1.5 ); % center horizontal diameter line 247 | an = annotation( 'line' ); 248 | set( an, 'Parent', a1 ); 249 | set( an, 'X', [ 0 0 ], 'Y', [ -1.5 * Rm, 1.5 * Rm ], 'LineStyle', '-.', 'LineWidth', 1.5 ); % center vertical diameter line 250 | for i = 0 : 1 251 | sgn = 1 - 2 * i; 252 | an = annotation( 'arrow' ); 253 | set( an, 'Parent', a1 ); 254 | set( an, 'X', [ 0, 0.4 * R ], 'Y', sgn * [ 1.5 * Rm, 1.5 * Rm ], 'LineWidth', 2 ); % projection arrow 255 | text( a1, -0.2 * R, sgn * 1.5 * Rm, 'A', 'FontSize', 30 ); 256 | end 257 | ei = 2; 258 | if D(1) == D(2) 259 | ei = 1; 260 | end 261 | for i = 1 : ei % loop over radii 262 | k = 1.75; 263 | if i == 2 264 | k = -k; 265 | end 266 | R = D(i)/2; 267 | an = annotation( 'line' ); 268 | set( an, 'Parent', a1 ); 269 | set( an, 'X', [ -R -R ], 'Y', [ 0, k * R ] ); % left diameter line 270 | an = annotation( 'line' ); 271 | set( an, 'Parent', a1 ); 272 | set( an, 'X', [ R R ], 'Y', [ 0, k * R ] ); % right diameter line 273 | an = annotation( 'textarrow', 'String', [ '\oslash ' num2str( D( i ) ) ], 'FontSize', 18 ); 274 | set( an, 'Parent', a1 ); 275 | set( an, 'X', [ 0.15 * R, R ], 'Y', [ k * R, k * R ] ); % diameter dimension double arrow 276 | an = annotation( 'arrow' ); 277 | set( an, 'Parent', a1 ); 278 | set( an, 'X', [ -0.2 * R, -R ], 'Y', [ k * R, k * R ] ); % diameter dimension double arrow 279 | end 280 | 281 | % cross-section dimensions 282 | Rm = max( D ) / 2; 283 | text( a2, mean( r ) - 1.2 * max( abs( [ p1 p2 ] ) ), 1.7 * Rm, 'Section A-A', 'FontSize', 30 ); 284 | k = 1.25; % overall dimensions margin scaling factor 285 | al = 0.25; % arrow length scaling factor 286 | ts = 0.1; % relative text shift wrt dimensions arrow 287 | for i = 1 : 2 288 | x = sort( r ); 289 | r0 = [ 0 0 ]; 290 | if i == 2 291 | k = -k; 292 | x = [ r(1) + p1( end ), r(2) + p2( end ) ]; 293 | r0 = -D / 2; 294 | end 295 | R = D(i)/2; 296 | an = annotation( 'line' ); 297 | set( an, 'Parent', a2 ); 298 | set( an, 'X', [ x(1) x(1) ], 'Y', [ r0(1), k * Rm ] ); % left thickness line 299 | an = annotation( 'line' ); 300 | set( an, 'Parent', a2 ); 301 | set( an, 'X', [ x(2) x(2) ], 'Y', [ r0(2), k * Rm ] ); % right thickness line 302 | if x(2) - x(1) > 0.5 * Rm % wide enough for double arrow 303 | an = annotation( 'doublearrow' ); 304 | set( an, 'Parent', a2 ); 305 | set( an, 'X', x, 'Y', [ k * Rm, k * Rm ] ); % diameter dimension double arrow 306 | else 307 | an = annotation( 'arrow' ); 308 | set( an, 'Parent', a2 ); 309 | set( an, 'X', [ x(1) - al * Rm, x(1) ], 'Y', [ k * Rm, k * Rm ] ); % diameter dimension double arrow 310 | an = annotation( 'arrow' ); 311 | set( an, 'Parent', a2 ); 312 | set( an, 'X', [ x(2) + al * Rm, x(2) ], 'Y', [ k * Rm, k * Rm ] ); % diameter dimension double arrow 313 | end 314 | text( a2, -0.1 * R + mean( x ), k * ( 1 + ts ) * Rm, num2str( x(2) - x(1), '%.2f' ), 'FontSize', 18 ); 315 | 316 | % display flange dimensions 317 | g = 1.5 * al * ( 2 * i - 3 ); 318 | if ~isempty( flange ) && ~( flange(1) == flange(2) && i == 1 ) % draw only one flange dimension if the flange is symmetric 319 | an = annotation( 'line' ); 320 | set( an, 'Parent', a2 ); 321 | x0 = r( i ) + pr{ i }( end ); 322 | set( an, 'X', [ x0 + g * Rm, x0 ], 'Y', -[ R, R ] ); % top thickness line 323 | an = annotation( 'line' ); 324 | set( an, 'Parent', a2 ); 325 | set( an, 'X', [ x0 + g * Rm, x0 ], 'Y', -flange( i ) - [ R, R ] ); % bottom thickness line 326 | an = annotation( 'arrow' ); 327 | set( an, 'Parent', a2 ); 328 | set( an, 'X', [ x0 + g * Rm, x0 + g * Rm ], 'Y', -[ R - al * Rm, R ] ); % diameter dimension double arrow 329 | an = annotation( 'arrow' ); 330 | set( an, 'Parent', a2 ); 331 | set( an, 'X', [ x0 + g * Rm, x0 + g * Rm ], 'Y', -flange( i ) - [ R + al * Rm, R ] ); % diameter dimension double arrow 332 | text( a2, x0 + g * Rm + ( i == 2 ) * 0.5 * ts * Rm - ( i == 1 ) * 3 * ts * Rm, -R - flange( i )/2, num2str( flange( i ), '%3.2f' ), 'FontSize', 18 ); 333 | end 334 | end 335 | 336 | % mark surfaces 337 | text( a2, r(1) - 0.25 * Rm, 0, 'S1', 'FontSize', 24 ); 338 | text( a2, r(2) + 0.05 * Rm, 0, 'S2', 'FontSize', 24 ); 339 | 340 | % print figure to pdf setting the right size and resolution for the print 341 | set( f, 'Units', 'inches' ); 342 | pos = get( f, 'Position'); 343 | set( f, 'PaperOrientation', 'landscape', 'PaperSize', [ pos(3) pos(4) ] ); 344 | print( f, [ name '.pdf' ], '-dpdf', '-r0' ); 345 | 346 | 347 | 348 | function circle( D, x, color, lw ) 349 | % draw concentric circle(s) of a given diameter and color at x depths 350 | [ ~, is ] = sort( x ); % sort by depth (increasing) 351 | D = D( is ); % order diameters 352 | for i = 1 : length( D ) 353 | pos = [ -D( i )/2, -D( i )/2, D( i ), D( i ) ]; 354 | if sum( D( 1 : i - 1 ) > D( i ) ) == 0 % all previous diameters were smaller 355 | rectangle( 'Position', pos, 'Curvature', [ 1 1 ], 'EdgeColor', color, 'LineWidth', lw ); % draw solid circle 356 | else 357 | rectangle( 'Position', pos, 'Curvature', [ 1 1 ], 'EdgeColor', color, 'LineWidth', 1, 'LineStyle', '--' ); % draw dashed circle 358 | end 359 | end 360 | axis equal; 361 | -------------------------------------------------------------------------------- /Bench.m: -------------------------------------------------------------------------------- 1 | classdef Bench < handle 2 | % Bench class implements a system of optical elements 3 | % A complex optical system can be stored and manipulated as a whole by 4 | % making it a Bench instance. 5 | % 6 | % Member functions: 7 | % 8 | % b = Bench( obj ) - constructor function 9 | % INPUT: 10 | % obj - an optical element, cell array of elements, or another bench 11 | % OUTPUT: 12 | % b - bench object 13 | % 14 | % b.display() - displays bench b's information 15 | % 16 | % b.draw( rays, draw_fl, alpha, scale, new_figure_fl ) - draws bench b in the current axes 17 | % INPUT: 18 | % rays - array of rays objects comprising full or partial light path 19 | % draw_fl - display rays as 'arrows' (default), 'lines', or 'rays' 20 | % alpha - opacity of optical surfaces from 0 to 1, default .33 21 | % scale - scale of the arrow heads for the 'arrows' draw_fl 22 | % new_figure_fl - 0, do not open, or 1, open (default) 23 | % 24 | % a = b.copy() - copies bench b to bench a 25 | % 26 | % b.append( a, n ) - appends element a to bench b n times. n > 1 27 | % corresponds to multiple possible interactions (internal reflections 28 | % and such). 29 | % 30 | % b.prepend( a, n ) - prepends element a to bench b n times 31 | % 32 | % b.replace( ind, a ) - replaces an element with index ind on bench b with element a 33 | % 34 | % b.remove( inds ) - removes elements located at inds on bench b 35 | % 36 | % b.rotate( rot_axis, rot_angle, rot_fl ) - rotate the bench b with all its elements 37 | % INPUT: 38 | % rot_axis - 1x3 vector defining the rotation axis 39 | % rot_angle - rotation angle (radians) 40 | % rot_fl - (0, default) rotation of the bench elements wrt to the 41 | % global origin, (1) rotation wrt to the bench geometric center 42 | % 43 | % b.translate( tr_vec ) - translate the bench b with all its elements 44 | % INPUT: 45 | % tr_vec - 1x3 translation vector 46 | % 47 | % rays_through = b.trace( rays_in, out_fl ) - trace rays through optical elements 48 | % on the bench b 49 | % INPUT: 50 | % rays_in - incoming rays, e.g., created by the Rays() function 51 | % out_fl - 0 include even rays that missed some elements on the 52 | % bench, - 1 (default) exlude such rays 53 | % OUTPUT: 54 | % rays_through - a cell array of refracted/reflected rays of the same 55 | % length as the number of optical elements on the bench. 56 | % 57 | % Copyright: Yury Petrov, 2016 58 | % 59 | 60 | properties 61 | elem = {}; % cell array of optical elements 62 | cnt = 0; % counter of elements in the system 63 | end 64 | 65 | methods 66 | function self = Bench( obj ) 67 | % b = Bench( obj ) - constructor function 68 | % INPUT: 69 | % obj - an optical element, cell array of elements, or another bench 70 | % OUTPUT: 71 | % b - bench object 72 | if nargin == 0 73 | return; 74 | end 75 | 76 | if isa( obj, 'Bench' ) % if another Bench 77 | obj = obj.elem; % extract elements 78 | end 79 | 80 | % append object(s) to the optical system 81 | nobj = length( obj ); 82 | for i = 1 : nobj 83 | self.cnt = self.cnt + 1; 84 | if nobj == 1 85 | self.elem{ self.cnt } = obj; 86 | elseif iscell( obj ) % other benches or cell arrays of Surfaces 87 | self.elem{ self.cnt } = obj{ i }; 88 | elseif isvector( obj ) % Rays 89 | self.elem{ self.cnt } = obj( i ); 90 | end 91 | end 92 | end 93 | 94 | function display( self ) 95 | % b.display() - displays bench b's information 96 | for i = 1 : self.cnt 97 | obj = self.elem{ i }; 98 | fprintf( '\n%s:\n', class( obj ) ); 99 | obj.display; 100 | end 101 | end 102 | 103 | function draw( self, rays, draw_fl, alpha, scale, new_figure_fl ) 104 | % b.draw( rays, draw_fl, alpha, scale, new_figure_fl ) - draws bench b in the current axes 105 | % INPUT: 106 | % rays - array of rays objects comprising full or partial light path 107 | % draw_fl - display rays as 'arrows' (default), 'lines', or 'rays' 108 | % alpha - opacity of optical surfaces from 0 to 1, default .33 109 | % scale - scale of the arrow heads for the 'arrows' draw_fl 110 | % new_figure_fl - 0, do not open, or 1, open (default) 111 | if nargin < 6 || isempty( new_figure_fl ) 112 | new_figure_fl = 1; % open a new figure by default 113 | end 114 | if nargin < 5 || isempty( scale ) 115 | if nargin > 1 116 | scale = ones( 1, length( rays ) ); 117 | else 118 | scale = 1; 119 | end 120 | else 121 | if length( scale ) == 1 122 | scale = repmat( scale, 1, length( rays ) ); % make all ones 123 | elseif length( scale ) < length( rays ) 124 | if size( scale, 1 ) > size( scale, 2 ) 125 | scale = scale'; 126 | end 127 | scale = [ scale ones( 1, length( rays ) - length( scale ) ) ]; % append ones 128 | end 129 | end 130 | if nargin < 4 || isempty( alpha ) 131 | alpha = 0.33; 132 | end 133 | if nargin < 3 || isempty( draw_fl ) 134 | draw_fl = 'arrows'; 135 | end 136 | if nargin < 2 || isempty( rays ) 137 | rays = []; 138 | end 139 | 140 | if new_figure_fl == 1 141 | fname = dbstack; % get debugging info 142 | [ ~, fname ] = fname.name; % get the second (original) call function name 143 | figure( 'Name', [ 'OPTOMETRIKA: ' fname ], 'NumberTitle', 'Off', ... 144 | 'Position', [ 0 0 1024 1024 ], ... 145 | 'Color', 'k' ); 146 | end 147 | hold on; 148 | for i = 1 : self.cnt 149 | obj = self.elem{ i }; 150 | if isprop( obj, 'glass' ) && ( strcmp( obj.glass{1}, 'soot' ) || strcmp( obj.glass{2}, 'soot' ) ) 151 | color = [ .25 .25 .25 1 ]; 152 | obj.draw( color ); 153 | else 154 | color = [ 1 1 1 alpha ]; 155 | obj.draw( color ); 156 | end 157 | end 158 | 159 | if ~isempty( rays ) 160 | if strcmp( draw_fl, 'lines' ) || strcmp( draw_fl, 'clines' ) || strcmp( draw_fl, 'rays' ) % draw ray bundles as lines 161 | if strcmp( draw_fl, 'lines' ) 162 | sym = '-'; 163 | else 164 | sym = '*:'; 165 | end 166 | for i = 1 : length( rays ) - 1 167 | vis = ( rays( i ).I ~= 0 ) & ... 168 | isfinite( sum( rays( i ).r.^2, 2 ) ) & ... 169 | isfinite( sum( rays( i + 1 ).r.^2, 2 ) ); % visible rays 170 | real = dot( rays( i + 1 ).r - rays( i ).r, rays( i ).n, 2 ) > 0; % real rays (vs. virtual for virtual image) 171 | [ unique_colors, ~, ic ] = unique( rays( i ).color, 'rows' ); 172 | for j = 1 : size( unique_colors, 1 ) 173 | cvis = vis & real & ( ic == j ); 174 | plot3( [ rays( i ).r( cvis, 1 )'; rays( i + 1 ).r( cvis, 1 )' ], ... 175 | [ rays( i ).r( cvis, 2 )'; rays( i + 1 ).r( cvis, 2 )' ], ... 176 | [ rays( i ).r( cvis, 3 )'; rays( i + 1 ).r( cvis, 3 )' ], sym, 'Color', unique_colors( j, : ) ); 177 | end 178 | end 179 | elseif strcmp( draw_fl, 'arrows' ) 180 | for i = 1 : length( rays ) 181 | rays( i ).draw( scale( i ) ); 182 | end 183 | end 184 | end 185 | 186 | %if new_figure_fl == 1 187 | gca.Clipping = 'off'; 188 | axis equal vis3d off; 189 | %grid on; 190 | camlight( 'left' ); 191 | camlight( 'right' ); 192 | camlight( 'headlight' ); 193 | view( -54, 54 ); 194 | lighting phong; 195 | rotate3d on; 196 | %end 197 | end 198 | 199 | function b = copy( self ) 200 | % a = b.copy() - copies bench b to bench a 201 | b = feval( class( self ) ); 202 | b.cnt = self.cnt; 203 | for i = 1 : length( self.elem ) 204 | b.elem{ i } = self.elem{ i }.copy; 205 | end 206 | end 207 | 208 | function append( self, obj, mult ) 209 | % b.append( a, n ) - appends element a to bench b n times. n > 1 210 | % corresponds to multiple possible interactions (internal reflections 211 | % and such). 212 | if nargin < 3 213 | mult = 1; 214 | end 215 | if isa( obj, 'Bench' ) % if another Bench 216 | obj = obj.elem; % extract elements 217 | end 218 | % append object(s) to the optical system 219 | nobj = length( obj ); 220 | for m = 1 : mult 221 | for i = 1 : nobj 222 | self.cnt = self.cnt + 1; 223 | if nobj == 1 224 | self.elem{ self.cnt } = obj; 225 | elseif iscell( obj ) % other benches or cell arrays of Surfaces 226 | self.elem{ self.cnt } = obj{ i }; 227 | elseif isvector( obj ) % Rays 228 | self.elem{ self.cnt } = obj( i ); 229 | end 230 | end 231 | end 232 | end 233 | 234 | function prepend( self, obj, mult ) 235 | % b.prepend( a, n ) - prepends element a to bench b n times 236 | if nargin < 3 237 | mult = 1; 238 | end 239 | if isa( obj, 'Bench' ) % if another Bench 240 | obj = obj.elem; % extract elements 241 | end 242 | self.elem = fliplr( self.elem ); % reverse element direction temporarily 243 | % prepend object(s) to the optical system 244 | nobj = length( obj ); 245 | for m = 1 : mult 246 | for i = nobj : -1 : 1 % append in the opposite order 247 | self.cnt = self.cnt + 1; 248 | if nobj == 1 249 | self.elem{ self.cnt } = obj; 250 | elseif iscell( obj ) % other benches or cell arrays of Surfaces 251 | self.elem{ self.cnt } = obj{ i }; 252 | elseif isvector( obj ) % Rays 253 | self.elem{ self.cnt } = obj( i ); 254 | end 255 | end 256 | end 257 | self.elem = fliplr( self.elem ); % restitute the original order 258 | end 259 | 260 | function replace( self, ind, obj ) 261 | % b.replace( ind, a ) - replaces an element with index ind on bench b with element a 262 | self.elem{ ind } = obj; 263 | end 264 | 265 | function remove( self, inds ) 266 | % b.remove( inds ) - removes elements located at inds on bench b 267 | if self.cnt == 0 268 | error( 'The bench is already empty!' ); 269 | else 270 | self.elem( inds ) = []; 271 | self.cnt = self.cnt - length( inds ); 272 | end 273 | end 274 | 275 | function rotate( self, rot_axis, rot_angle, rot_fl ) 276 | % b.rotate( rot_axis, rot_angle, rot_fl ) - rotate the bench b with all its elements 277 | % INPUT: 278 | % rot_axis - 1x3 vector defining the rotation axis 279 | % rot_angle - rotation angle (radians) 280 | % rot_fl - (0, default) rotation of the bench elements wrt to the 281 | % global origin, (1) rotation wrt to the bench geometric center 282 | if nargin < 4 283 | rot_fl = 0; 284 | end 285 | cntr = [ 0 0 0 ]; 286 | if rot_fl == 1 % rotate around the geometric center of the bench 287 | for i = 1 : self.cnt % loop through the optic system 288 | cntr = cntr + self.elem{ i }.r; 289 | end 290 | cntr = cntr / self.cnt; 291 | end 292 | % rotate bench elements 293 | for i = 1 : self.cnt % loop through the optic system 294 | self.elem{ i }.rotate( rot_axis, rot_angle ); % rotate normal 295 | self.elem{ i }.r = cntr + rodrigues_rot( self.elem{ i }.r - cntr, rot_axis, rot_angle ); % rotate position 296 | end 297 | if abs( rot_angle ) > pi/2 % reverse order in which the elements are encountered by rays 298 | self.elem = fliplr( self.elem ); 299 | end 300 | end 301 | 302 | function translate( self, tr_vec ) 303 | % b.translate( tr_vec ) - translate the bench b with all its elements 304 | % INPUT: 305 | % tr_vec - 1x3 translation vector 306 | for i = 1 : self.cnt % loop through the optic system 307 | self.elem{ i }.r = self.elem{ i }.r + tr_vec; % translate position 308 | end 309 | end 310 | 311 | function rays = trace( self, rays_in, out_fl ) 312 | % rays_through = b.trace( rays_in, out_fl ) - trace rays through optical elements 313 | % on the bench b 314 | % INPUT: 315 | % rays_in - incoming rays, e.g., created by the Rays() function 316 | % out_fl - 0 include even rays that missed some elements on the 317 | % bench, - 1 (default) exlude such rays 318 | % OUTPUT: 319 | % rays_through - a cell array of refracted/reflected rays of the same 320 | % length as the number of optical elements on the bench. 321 | if nargin < 3 322 | out_fl = 1; % exclude rays which miss elements of the bench 323 | end 324 | rays( 1, self.cnt + 1 ) = Rays; % allocate cnt instances of Rays 325 | rays( 1 ) = rays_in; 326 | for i = 1 : self.cnt % loop through the optic system 327 | rays( i + 1 ) = rays( i ).interaction( self.elem{ i }, out_fl ); 328 | end 329 | end 330 | end 331 | 332 | end 333 | 334 | -------------------------------------------------------------------------------- /FresnelLens.m: -------------------------------------------------------------------------------- 1 | classdef FresnelLens < Surface 2 | % FRESNELLENS Implements a Fresnel lens surface given by a rotation of 3 | % a piecewize linear curve defining a set of cone segments. These are 4 | % characterized by radii, sag points, and half-angles measured with 5 | % respect to the lens axis of rotation. 6 | % 7 | % Member functions: 8 | % 9 | % l = FresnelLens( r, rad, sag, the, glass, walls, inner ) - object constructor 10 | % INPUT: 11 | % r - 1x3 position vector 12 | % rad - ncones x 1 vector of outer radii for the Fresnel cones 13 | % sag - ncones x 1 vector of sag values for the inner cone edge 14 | % the - ncones x 3 matrix of cone half-angles (radians) and curvature 15 | % radius (in units of the outer ring radius). 16 | % If the second column is absent, its half angles are assuned 17 | % to be equal to pi, i.e., the Fresnel walls - vertical cylinders. 18 | % If the third column is absent, it's assumed to be Inf, i.e., flat 19 | % conical rings. 20 | % Values in the second column less than pi indicate conical walls sloping 21 | % toward the lens periphery, the values more than pi - conical walls 22 | % sloping toward the lens center (overhanging). 23 | % glass - 1 x 2 cell array of strings, e.g., { 'air' 'acrylic' } 24 | % walls - 0 for soot Fresnel walls (absorbs all light), 1 for walls of the same 25 | % material as the rest of the lens 26 | % inner - 0 for inner Fresnel diameter = 0, 1 for inner Fresnel 27 | % diameter = 2 * ( 2 * rad(1) - rad(2) ), i.e. defined by the smallest 28 | % inner radius of the Fresnel ring 29 | % 30 | % OUTPUT: 31 | % l - lens surface object 32 | % 33 | % l.display() - displays the surface l information 34 | % 35 | % l.draw() - draws the surface l in the current axes 36 | % 37 | % l.rotate( rot_axis, rot_angle ) - rotate the surface l 38 | % INPUT: 39 | % rot_axis - 1x3 vector defining the rotation axis 40 | % rot_angle - rotation angle (radians) 41 | % 42 | % Copyright: Yury Petrov, 2017 43 | % 44 | 45 | properties 46 | D = [ 0; 1 ]; % lens diameter (inner, outer) 47 | ncones = 0; % number of Fresnel cones 48 | rad = []; % cone radii 49 | sag = []; % cone sags 50 | the = []; % cone half-angles 51 | vx = []; % conic vertex (tip of ellipsoid, parabola, hyperbola) x position 52 | walls = 1; % consider Fresnel walls by default 53 | possible = true; % if the lens is possible or not 54 | end 55 | 56 | methods 57 | function self = FresnelLens( ar, aR, as, ath, aglass, awalls, inner ) 58 | if nargin == 0 59 | return; 60 | end 61 | if nargin < 6 62 | awalls = 1; % consider Fresnel walls by default 63 | end 64 | 65 | if size( aR, 1 ) < size( aR, 2 ) 66 | aR = aR'; 67 | end 68 | if size( as, 1 ) < size( as, 2 ) 69 | as = as'; 70 | end 71 | if size( ath, 1 ) < size( ath, 2 ) 72 | ath = ath'; 73 | end 74 | if size( aR, 1 ) ~= size( as, 1 ) || ... 75 | size( aR, 1 ) ~= size( ath, 1 ) || ... 76 | size( as, 1 ) ~= size( ath, 1 ) 77 | error( 'Fresnel lens R, s, and th vectors must have the same length!' ); 78 | else 79 | self.ncones = size( aR, 1 ); 80 | end 81 | % sort all vectors based on ascending radii 82 | [ aR, ind ] = sort( aR ); 83 | as = as( ind ); 84 | ath = ath( ind, : ); 85 | if size( ath, 2 ) == 1 86 | ath = [ ath pi * ones( size( ath ) ) ]; % default to vertical walls 87 | end 88 | 89 | if nargin < 7 || inner == 0 90 | self.D(1) = 0; 91 | else 92 | self.D(1) = 2 * ( aR( 1 ) - ( aR(2) - aR(1) ) ); % inner radius, assuming first and second rings have the same width 93 | end 94 | self.D(2) = 2 * aR( end ); % outer radius 95 | 96 | self.r = ar; 97 | self.sag = as; 98 | self.the = ath( :, 1:2 ); 99 | self.walls = awalls; 100 | 101 | if size( ath, 2 ) == 3 % curvature specified 102 | self.R = ath( :, 3 ) .* aR; % radius of curvature 103 | rs = [ self.D(1)/2; aR ]; 104 | rs = rs( 1 : end - 1 ); % inner radii 105 | self.k = ( self.R ./ rs ).^2 - tan( self.the( :, 1 ) ).^2 - 1; % conic constants 106 | self.vx = -self.R ./ ( 1 + self.k ) .* ( 1 - tan( self.the( :, 1 ) ) .* rs ./ self.R ); % quadric vertices using the inner radius 107 | if rs( 1 ) == 0 % k cannot be defined by the slope at the center of the quadric, take it explicitely 108 | self.k( 1 ) = self.the( 1, 1 ); 109 | self.the( 1 ) = pi/2; 110 | self.vx( 1 ) = 0; % because the inner radius is zero here 111 | end 112 | % treat parabolas specially 113 | parabola = abs( self.k + 1 ) < 1e-10; 114 | ellipse = self.k + 1 >= 1e-10; 115 | self.k( parabola ) = -1; % round to avoid errors at the ray-tracing step 116 | self.vx( parabola ) = -rs( parabola ).^2 ./ ( 2 * self.R( parabola ) ); 117 | if parabola( 1 ) && rs( 1 ) == 0 118 | self.vx( 1 ) = -rs( 2 ).^2 ./ ( 2 * self.R( 1 ) ); 119 | end 120 | 121 | % find intersection of the quadric with the wall cone 122 | sl = tan( pi/2 - self.the( :, 2 ) ); % slope of the wall 123 | g = 1 ./ ( 1 + self.k ); 124 | dd = circshift( self.sag, -1 ) - self.sag; 125 | dd( end ) = dd( end - 1 ); % approximate with linear here 126 | z0 = dd - self.vx; 127 | a = sl.^2 + g; 128 | b = 2 * sl .* ( z0 - self.R .* g - aR .* sl ); 129 | c = sl.^2 .* aR.^2 - 2 * sl .* aR .* ( z0 - self.R .* g ) + z0.^2 - 2 * z0 .* self.R .* g; 130 | % treat parabolas specially 131 | a( parabola ) = 1 ./ ( 2 * self.R( parabola ) ); 132 | b( parabola ) = -sl( parabola ); 133 | c( parabola ) = sl( parabola ) .* aR( parabola ) - z0( parabola ); 134 | % solve quadratic equation 135 | D2 = b.^2 - 4 * a .* c; 136 | D = sqrt( D2 ); 137 | sgn = 2 * ( self.the( :, 2 ) > pi ) - 1; 138 | sgn( parabola | ellipse ) = -sgn( parabola | ellipse ); 139 | rm = 0.5 * ( -b + sgn .* sign( self.R ) .* D ) ./ a; % use the smaller of the two intersections 140 | dd = rm - circshift( aR, 1 ); % distance between the wall intersection and the previous outer radius 141 | if sum( dd( 2 : end ) < 0 ) ~= 0 % the quadric curves back 142 | self.possible = false; 143 | end 144 | % rm( end ) = aR( end ); % vertical wall for the last ring 145 | aR = [ aR rm ]; 146 | % deal with vertical walls (cylinders) 147 | vertical = abs( self.the( :, 2 ) - pi ) < 1e-10; 148 | aR( vertical, 2 ) = aR( vertical, 1 ); % the same radius here 149 | % check if the intersections are possible 150 | if sum( D2( ~vertical ) < 0 ) > 0 151 | self.possible = false; 152 | end 153 | 154 | else % conic rings 155 | % find intersection of the Fresnel cone with the wall cone 156 | if size( ath, 2 ) > 1 % non-cylindrical (conic) walls 157 | %tga = tan( pi - ath( :, 1 ) ); 158 | %tth = tan( ath( :, 2 ) ); 159 | tga = tan( ath( :, 1 ) ); 160 | tth = tan( pi - ath( :, 2 ) ); 161 | dr = aR - circshift( aR, 1 ); 162 | dr( 1 ) = aR( 1 ); 163 | ds = as - circshift( as, 1 ); 164 | ds( 1 ) = as( 1 ); 165 | dx = -tth .* ( dr ./ tga - ds ) ./ ( tth ./ tga + 1 ); % distance from the next radius aR to the intersection of the Fresnel slope and wall 166 | aR = [ aR, aR + dx ]; % the first radius is the value where the next Fresnel slope starts, 167 | % the second value is where the current Fresnel slope ends and the Fresnel wall starts 168 | else 169 | aR = [ aR, aR ]; 170 | ath = [ ath, repmat( pi, size( ath, 1 ), 1 ) ]; 171 | end 172 | end 173 | 174 | self.rad = aR; 175 | self.glass = aglass; 176 | end 177 | 178 | function display( self ) 179 | fprintf( 'Position:\t [%.3f %.3f %.3f]\n', self.r ); 180 | fprintf( 'Orientation:\t [%.3f %.3f %.3f]\n', self.n ); 181 | fprintf( 'Diameter:\t %.3f\n', self.D(2) ); 182 | if self.D(1) ~= 0 183 | fprintf( 'Inner diameter:\t %.3f\n', self.D(1) ); 184 | end 185 | fprintf( 'Number of Fresnel rings:\t %i\n', self.ncones ); 186 | fprintf( 'Steepest slope (rad):\t %i\n', max( abs( self.the( :, 1 ) ) ) ); 187 | fprintf( 'Material:\t %s | %s\n', self.glass{ 1 }, self.glass{ 2 } ); 188 | end 189 | 190 | function h = draw( self, color ) 191 | % DISPLAY the Fresnel lens surface 192 | if nargin < 2 193 | color = [ 1 1 1 .5 ]; 194 | end 195 | nang = 100; 196 | as = linspace( 0, 2 * pi, nang ); 197 | nrad = 100; 198 | h = zeros( self.ncones, 1 ); 199 | cnt = 0; % surface counter 200 | 201 | % F = []; 202 | % V = []; 203 | for i = 1 : self.ncones % loop over cones 204 | zc = []; 205 | if i == 1 206 | if self.D(1) == 0 207 | radin = 0; 208 | else 209 | radin = 2 * self.rad( 1, 1 ) - self.rad( 2, 1 ); % take the inner radius assuming the same step 210 | end 211 | else 212 | radin = self.rad( i - 1, 1 ); 213 | end 214 | 215 | if isempty( self.vx ) || self.R( i, 1 ) == 0 || isinf( self.k( i ) ) % cone surface 216 | [ x, y, z ] = cylinder( [ radin self.rad( i, 2 ) ], nang ); 217 | z( 1, : ) = self.sag( i ); 218 | z( 2, : ) = self.sag( i ) + ( self.rad( i, 2 ) - radin ) / tan( self.the( i, 1 ) ); 219 | [ xc, yc ] = cylinder( [ self.rad( i, 2 ) self.rad( i, 1 ) ], nang ); % Fresnel wall 220 | else % quadric surface 221 | rads = linspace( radin, self.rad( i, 2 ), nrad ); 222 | [ angs, rads ] = meshgrid( as, rads ); 223 | [ x, y ] = pol2cart( angs, rads ); 224 | a = 1 + self.k( i ); 225 | if length( self.R( i, : ) ) == 1 226 | r2xy = ( x.^2 + y.^2 ) / self.R( i )^2; 227 | if abs( a ) < 1e-10 % paraboloid, special case 228 | z = r2xy * self.R( i, 1 ) / 2; 229 | else 230 | z = self.R( i ) / a * ( 1 - sqrt( 1 - a * r2xy ) ); 231 | % if sum( ~isreal( z ) ) ~= 0 232 | % a; 233 | % end 234 | end 235 | else % asymmetric conic 236 | r2xy = x.^2 / self.R( i, 1 ) + y.^2 / self.R( i, 2 ); 237 | if abs( a ) < 1e-10 % paraboloid, special case 238 | z = r2xy / 2; 239 | else 240 | z = r2xy ./ ( 1 + sqrt( 1 - a * ( x.^2 / self.R( i, 1 )^2 + self.R( i, 2 ) / self.R( i, 1 ) * y.^2 / self.R( i, 2 )^2 ) ) ); 241 | end 242 | end 243 | z = z + self.sag( i ) + self.vx( i ); % add sag and quadric vertex displacement 244 | [ xc, yc ] = cylinder( [ self.rad( i, 2 ) self.rad( i, 1 ) ], nang - 1 ); % Fresnel wall 245 | end 246 | 247 | % create the cylinder wall 248 | zc( 1, : ) = z( end, : ); 249 | if i == self.ncones 250 | zc( 2, : ) = zc( 1, : ); % no wall for the last ring 251 | else 252 | zc( 2, : ) = self.sag( i + 1 ); % add sag to the first point 253 | end 254 | 255 | if abs( self.the( i, 1 ) - pi/2 ) > 1e-10 && ( sign( ( z( 2, 1 ) - z( 1, 1 ) ) / ( self.rad( i, 2 ) - radin ) ) ~= sign( tan( self.the( i, 1 ) ) ) ) 256 | z = flipud( z ); 257 | end 258 | if sign( ( zc( 2, 1 ) - zc( 1, 1 ) ) / ( self.rad( i, 1 ) - self.rad( i, 2 ) ) ) ~= sign( tan( self.the( i, 2 ) ) ) 259 | zc = flipud( zc ); 260 | end 261 | S = [ z(:) -y(:) x(:) ]; % put the cone into the Optometrika reference frame 262 | Sc = [ zc(:) -yc(:) xc(:) ]; % put the wall into the Optometrika reference frame 263 | 264 | % rotate and shift 265 | if self.rotang ~= 0 266 | S = rodrigues_rot( S, self.rotax, self.rotang ); 267 | Sc = rodrigues_rot( Sc, self.rotax, self.rotang ); 268 | end 269 | x(:) = S( :, 1 ) + self.r( 1 ); 270 | y(:) = S( :, 2 ) + self.r( 2 ); 271 | z(:) = S( :, 3 ) + self.r( 3 ); 272 | xc(:) = Sc( :, 1 ) + self.r( 1 ); 273 | yc(:) = Sc( :, 2 ) + self.r( 2 ); 274 | zc(:) = Sc( :, 3 ) + self.r( 3 ); 275 | 276 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( x, 1 ), size( x, 2 ), 1 ); 277 | cnt = cnt + 1; 278 | if sum( ~isreal( x ) + ~isreal( y ) + ~isreal( z ) ) ~= 0 279 | error( 'Complex surface!' ); 280 | end 281 | h( cnt ) = surf( x, y, z, c, ... 282 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 283 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 284 | fv = surf2patch( x, y, z, 'triangles' ); 285 | %F = [ F; fv.faces ]; 286 | %V = [ V; fv.vertices ]; 287 | c = repmat( reshape( color( 1:3 ), [ 1 1 3 ] ), size( xc, 1 ), size( xc, 2 ), 1 ); 288 | cnt = cnt + 1; 289 | h( cnt ) = surf( xc, yc, zc, c, ... 290 | 'EdgeColor', 'none', 'FaceLighting','phong', 'FaceColor', 'interp', 'FaceAlpha', color(4), ... 291 | 'AmbientStrength', 0., 'SpecularStrength', 1 ); % grey color, shiny 292 | fv = surf2patch( xc, yc, zc, 'triangles' ); 293 | %F = [ F; fv.faces ]; 294 | %V = [ V; fv.vertices ]; 295 | end 296 | 297 | %stlwrite( 'test.stl', F, V ); 298 | end 299 | 300 | function rotate( self, rot_axis, rot_angle ) 301 | self.rotate@Surface( rot_axis, rot_angle ); % rotate the surface members 302 | if abs( rot_angle ) > pi/2 303 | self.the = pi - self.the; 304 | self.the( self.the == 0 ) = pi; 305 | self.sag = -self.sag; 306 | self.vx = -self.vx; 307 | end 308 | end 309 | 310 | end 311 | 312 | end 313 | 314 | --------------------------------------------------------------------------------