mirror of
https://github.com/bspeice/speice.io
synced 2025-04-27 16:21:30 -04:00
1 line
49 KiB
JavaScript
1 line
49 KiB
JavaScript
"use strict";(self.webpackChunkspeice_io=self.webpackChunkspeice_io||[]).push([["7518"],{56200:function(e,s,a){a.d(s,{Z:function(){return n}});let n={inputGroup:"inputGroup_aXxM",inputTitle:"inputTitle_L5pB",inputElement:"inputElement_lfVV",inputReset:"inputReset_vh8n"}},58643:function(e,s,a){a.r(s),a.d(s,{default:()=>b,frontMatter:()=>g,metadata:()=>n,assets:()=>y,toc:()=>w,contentTitle:()=>N});var n=a("33671"),t=a("85893"),l=a("50065"),r=a("47333"),i=a("88100"),c=a("67294"),m=a("31224"),h=a("72373"),o=a("86539"),d=a("84896"),x=a("9976"),p=a("2301"),j=a("17885"),u=a("56200");function f(e){let{children:s}=e,{width:a,height:n,setPainter:l}=(0,c.useContext)(i.wn),r=Math.max(a,n)/4,[f,g]=c.useState(0),[N,y]=c.useState(0),[w,v]=c.useState(0),[b,E]=c.useState(0),M=(0,t.jsx)("button",{className:u.Z.inputReset,onClick:()=>{g(0),y(0),v(0),E(0)},children:"Reset"});return(0,c.useEffect)(()=>{l(function*(e){let{width:s,height:a,transforms:n,final:t,palette:l,colors:r,finalColor:i,positionX:c,positionY:m,rotate:u,zoom:f,scale:g}=e,N=s*a,y=Array(N).fill(0),w=Array(N).fill(0),v=Array(N).fill(0),b=Array(N).fill(0),E=(e,n,t)=>{var r,i;let[h,o]=(r=e,i=n,[r,i]=[r-c,i-m],[r,i]=[r*Math.cos(u)-i*Math.sin(u),r*Math.sin(u)+i*Math.cos(u)],[r,i]=[r*Math.pow(2,f),i*Math.pow(2,f)],[Math.floor(r*g+s/2),Math.floor(i*g+a/2)]);if(h<0||h>=s||o<0||o>=s)return;let p=(0,d.j)(h,o,s,1),[j,N,E]=(0,x.w)(l,t);y[p]+=j,w[p]+=N,v[p]+=E,b[p]+=1},[M,A]=[(0,h.J)(),(0,h.J)()],k=Math.random(),B=10*N;for(let e=0;e<B;e++){let[l,c]=(0,o.m)(n);[M,A]=c(M,A);let m=r[l];k=(0,p.R)(k,m.color,m.colorSpeed);let[h,d]=t(M,A),x=(0,p.R)(k,i.color,i.colorSpeed);e>20&&E(h,d,x),e%1e5==0&&(yield(0,j.g)(s,a,y,w,v,b))}yield(0,j.g)(s,a,y,w,v,b)}({width:a,height:n,transforms:m.y7,final:m.SV,palette:m.DG,colors:[{color:m.N3,colorSpeed:.5},{color:m.yV,colorSpeed:.5},{color:m.iD,colorSpeed:.5}],finalColor:{color:m.sB,colorSpeed:.5},scale:r,zoom:f,rotate:-N/180*Math.PI,positionX:w,positionY:b}))},[r,f,N,w,b]),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("div",{className:u.Z.inputGroup,style:{display:"grid",gridTemplateColumns:"1fr 1fr"},children:[(0,t.jsxs)("p",{className:u.Z.inputTitle,style:{gridColumn:"1/-1"},children:["Camera ",M]}),(0,t.jsxs)("div",{className:u.Z.inputElement,children:[(0,t.jsxs)("p",{children:["Zoom: ",f]}),(0,t.jsx)("input",{type:"range",min:-.5,max:2,step:.01,value:f,onInput:e=>g(Number(e.currentTarget.value))})]}),(0,t.jsxs)("div",{className:u.Z.inputElement,children:[(0,t.jsxs)("p",{children:["Rotate (deg): ",N]}),(0,t.jsx)("input",{type:"range",min:0,max:360,step:1,value:N,onInput:e=>y(Number(e.currentTarget.value))})]}),(0,t.jsxs)("div",{className:u.Z.inputElement,children:[(0,t.jsxs)("p",{children:["Offset X: ",w]}),(0,t.jsx)("input",{type:"range",min:-2,max:2,step:.01,value:w,onInput:e=>v(Number(e.currentTarget.value))})]}),(0,t.jsxs)("div",{className:u.Z.inputElement,children:[(0,t.jsxs)("p",{children:["Offset Y: ",b]}),(0,t.jsx)("input",{type:"range",min:-2,max:2,step:.01,value:b,onInput:e=>E(Number(e.currentTarget.value))})]})]}),s]})}let g={slug:"2025/03/playing-with-fire-camera",title:"Playing with fire: The camera",date:new Date("2025-03-10T12:00:00.000Z"),authors:["bspeice"],tags:[]},N=void 0,y={authorsImageUrls:[void 0]},w=[{value:"Restrictions",id:"restrictions",level:2},{value:"Parameters",id:"parameters",level:2},{value:"Position",id:"position",level:3},{value:"Rotation",id:"rotation",level:3},{value:"Zoom",id:"zoom",level:3},{value:"Scale",id:"scale",level:3},{value:"Camera",id:"camera",level:2},{value:"Summary",id:"summary",level:2}];function v(e){let s={a:"a",admonition:"admonition",annotation:"annotation",blockquote:"blockquote",code:"code",h2:"h2",h3:"h3",img:"img",li:"li",math:"math",mi:"mi",mn:"mn",mo:"mo",mrow:"mrow",mstyle:"mstyle",mtable:"mtable",mtd:"mtd",mtext:"mtext",mtr:"mtr",p:"p",pre:"pre",semantics:"semantics",span:"span",ul:"ul",...(0,l.a)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(s.p,{children:["Something that bugged me while writing the first three articles on fractal flames were the constraints on\noutput images. At the time, I had worked out how to render fractal flames by studying\n",(0,t.jsx)(s.a,{href:"https://sourceforge.net/projects/apophysis/",children:"Apophysis"})," and ",(0,t.jsx)(s.a,{href:"https://github.com/scottdraves/flam3",children:"flam3"}),"; just enough to display images\nin a browser."]}),"\n",(0,t.jsx)(s.p,{children:"Having spent more time with fractal flames and computer graphics, it's time to implement\nsome missing features."}),"\n",(0,t.jsx)(s.h2,{id:"restrictions",children:"Restrictions"}),"\n",(0,t.jsx)(s.p,{children:"To review, the restrictions we've had so far:"}),"\n",(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsxs)(s.p,{children:["...we need to convert from fractal flame coordinates to pixel coordinates.\nTo simplify things, we'll assume that we're plotting a square image with range ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[0,1]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]})," for both x and y"]}),"\n",(0,t.jsxs)(s.p,{children:["-- ",(0,t.jsx)(s.a,{href:"/2024/11/playing-with-fire",children:"The fractal flame algorithm"})]}),"\n"]}),"\n",(0,t.jsx)(s.p,{children:"First, we've assumed that fractals get displayed in a square image. Ignoring aspect ratios simplifies\nthe render process, but we don't usually want square images. It's possible to render a large\nsquare image and crop it to fit, but we'd rather render directly to the desired size."}),"\n",(0,t.jsxs)(s.p,{children:["Second, we've assumed that fractals have a pre-determined display range. For Sierpinski's Gasket,\nthat was ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[0, 1]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]})," (the reference parameters used a range of ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[-2, 2]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]}),"). However, if we could\ncontrol the display range, it would let us zoom in and out of the image."]}),"\n",(0,t.jsx)(s.h2,{id:"parameters",children:"Parameters"}),"\n",(0,t.jsx)(s.p,{children:"For comparison, the camera controls available in Apophysis offer a lot of flexibility:"}),"\n",(0,t.jsx)("center",{children:(0,t.jsx)(s.img,{alt:"Screenshot of Apophysis camera controls",src:a(5749).Z+"",width:"390",height:"293"})}),"\n",(0,t.jsx)(s.p,{children:"The remaining parameters to implement are: position (X and Y), rotation, zoom, and scale."}),"\n",(0,t.jsx)(s.h3,{id:"position",children:"Position"}),"\n",(0,t.jsxs)(s.p,{children:["Fractal flames normally use ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"(0, 0)"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})})]})," as the image center. The position parameters (X and Y) move\nthe center point, which effectively pans the image. A positive X position shifts left, and a\nnegative X position shifts right. Similarly, a positive Y position shifts up, and a negative\nY position shifts the image down."]}),"\n",(0,t.jsx)(s.p,{children:"To apply the position parameters, simply subtract them from each point in the chaos game prior to plotting it:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-typescript",children:"[x, y] = [\n x - positionX,\n y - positionY\n];\n"})}),"\n",(0,t.jsx)(s.h3,{id:"rotation",children:"Rotation"}),"\n",(0,t.jsxs)(s.p,{children:["After the position parameters, we can rotate the image around the (new) center point. To do so, we'll go back to the\n",(0,t.jsx)(s.a,{href:"https://en.wikipedia.org/wiki/Affine_transformation",children:"affine transformations"})," we've been using so far.\nSpecifically, the rotation angle ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsx)(s.mrow,{children:(0,t.jsx)(s.mi,{children:"\u03B8"})}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"\\theta"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"0.6944em"}}),(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.02778em"},children:"\u03B8"})]})})]})," gives us a transform matrix we can apply prior to plotting:"]}),"\n",(0,t.jsx)(s.span,{className:"katex-display",children:(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",display:"block",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{fence:"true",children:"["}),(0,t.jsxs)(s.mtable,{rowspacing:"0.16em",columnalign:"center center",columnspacing:"1em",children:[(0,t.jsxs)(s.mtr,{children:[(0,t.jsx)(s.mtd,{children:(0,t.jsx)(s.mstyle,{scriptlevel:"0",displaystyle:"false",children:(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mtext,{children:"cos"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mi,{children:"\u03B8"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]})})}),(0,t.jsx)(s.mtd,{children:(0,t.jsx)(s.mstyle,{scriptlevel:"0",displaystyle:"false",children:(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsx)(s.mtext,{children:"sin"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mi,{children:"\u03B8"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]})})})]}),(0,t.jsxs)(s.mtr,{children:[(0,t.jsx)(s.mtd,{children:(0,t.jsx)(s.mstyle,{scriptlevel:"0",displaystyle:"false",children:(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mtext,{children:"sin"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mi,{children:"\u03B8"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]})})}),(0,t.jsx)(s.mtd,{children:(0,t.jsx)(s.mstyle,{scriptlevel:"0",displaystyle:"false",children:(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mtext,{children:"cos"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mi,{children:"\u03B8"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]})})})]})]}),(0,t.jsx)(s.mo,{fence:"true",children:"]"})]}),(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{fence:"true",children:"["}),(0,t.jsxs)(s.mtable,{rowspacing:"0.16em",columnalign:"center",columnspacing:"1em",children:[(0,t.jsx)(s.mtr,{children:(0,t.jsx)(s.mtd,{children:(0,t.jsx)(s.mstyle,{scriptlevel:"0",displaystyle:"false",children:(0,t.jsx)(s.mi,{children:"x"})})})}),(0,t.jsx)(s.mtr,{children:(0,t.jsx)(s.mtd,{children:(0,t.jsx)(s.mstyle,{scriptlevel:"0",displaystyle:"false",children:(0,t.jsx)(s.mi,{children:"y"})})})})]}),(0,t.jsx)(s.mo,{fence:"true",children:"]"})]})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"\\begin{bmatrix}\n\\text{cos}(\\theta) & -\\text{sin}(\\theta) \\\\\n\\text{sin}(\\theta) & \\text{cos}(\\theta)\n\\end{bmatrix}\n\n\\begin{bmatrix}\nx \\\\\ny\n\\end{bmatrix}"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"2.4em",verticalAlign:"-0.95em"}}),(0,t.jsxs)(s.span,{className:"minner",children:[(0,t.jsx)(s.span,{className:"mopen delimcenter",style:{top:"0em"},children:(0,t.jsx)(s.span,{className:"delimsizing size3",children:"["})}),(0,t.jsx)(s.span,{className:"mord",children:(0,t.jsxs)(s.span,{className:"mtable",children:[(0,t.jsx)(s.span,{className:"col-align-c",children:(0,t.jsxs)(s.span,{className:"vlist-t vlist-t2",children:[(0,t.jsxs)(s.span,{className:"vlist-r",children:[(0,t.jsxs)(s.span,{className:"vlist",style:{height:"1.45em"},children:[(0,t.jsxs)(s.span,{style:{top:"-3.61em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"3em"}}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord text",children:(0,t.jsx)(s.span,{className:"mord",children:"cos"})}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.02778em"},children:"\u03B8"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})]}),(0,t.jsxs)(s.span,{style:{top:"-2.41em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"3em"}}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord text",children:(0,t.jsx)(s.span,{className:"mord",children:"sin"})}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.02778em"},children:"\u03B8"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})]})]}),(0,t.jsx)(s.span,{className:"vlist-s",children:"\u200B"})]}),(0,t.jsx)(s.span,{className:"vlist-r",children:(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.95em"},children:(0,t.jsx)(s.span,{})})})]})}),(0,t.jsx)(s.span,{className:"arraycolsep",style:{width:"0.5em"}}),(0,t.jsx)(s.span,{className:"arraycolsep",style:{width:"0.5em"}}),(0,t.jsx)(s.span,{className:"col-align-c",children:(0,t.jsxs)(s.span,{className:"vlist-t vlist-t2",children:[(0,t.jsxs)(s.span,{className:"vlist-r",children:[(0,t.jsxs)(s.span,{className:"vlist",style:{height:"1.45em"},children:[(0,t.jsxs)(s.span,{style:{top:"-3.61em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"3em"}}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mord text",children:(0,t.jsx)(s.span,{className:"mord",children:"sin"})}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.02778em"},children:"\u03B8"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})]}),(0,t.jsxs)(s.span,{style:{top:"-2.41em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"3em"}}),(0,t.jsxs)(s.span,{className:"mord",children:[(0,t.jsx)(s.span,{className:"mord text",children:(0,t.jsx)(s.span,{className:"mord",children:"cos"})}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.02778em"},children:"\u03B8"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})]})]}),(0,t.jsx)(s.span,{className:"vlist-s",children:"\u200B"})]}),(0,t.jsx)(s.span,{className:"vlist-r",children:(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.95em"},children:(0,t.jsx)(s.span,{})})})]})})]})}),(0,t.jsx)(s.span,{className:"mclose delimcenter",style:{top:"0em"},children:(0,t.jsx)(s.span,{className:"delimsizing size3",children:"]"})})]}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsxs)(s.span,{className:"minner",children:[(0,t.jsx)(s.span,{className:"mopen delimcenter",style:{top:"0em"},children:(0,t.jsx)(s.span,{className:"delimsizing size3",children:"["})}),(0,t.jsx)(s.span,{className:"mord",children:(0,t.jsx)(s.span,{className:"mtable",children:(0,t.jsx)(s.span,{className:"col-align-c",children:(0,t.jsxs)(s.span,{className:"vlist-t vlist-t2",children:[(0,t.jsxs)(s.span,{className:"vlist-r",children:[(0,t.jsxs)(s.span,{className:"vlist",style:{height:"1.45em"},children:[(0,t.jsxs)(s.span,{style:{top:"-3.61em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"3em"}}),(0,t.jsx)(s.span,{className:"mord",children:(0,t.jsx)(s.span,{className:"mord mathnormal",children:"x"})})]}),(0,t.jsxs)(s.span,{style:{top:"-2.41em"},children:[(0,t.jsx)(s.span,{className:"pstrut",style:{height:"3em"}}),(0,t.jsx)(s.span,{className:"mord",children:(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.03588em"},children:"y"})})]})]}),(0,t.jsx)(s.span,{className:"vlist-s",children:"\u200B"})]}),(0,t.jsx)(s.span,{className:"vlist-r",children:(0,t.jsx)(s.span,{className:"vlist",style:{height:"0.95em"},children:(0,t.jsx)(s.span,{})})})]})})})}),(0,t.jsx)(s.span,{className:"mclose delimcenter",style:{top:"0em"},children:(0,t.jsx)(s.span,{className:"delimsizing size3",children:"]"})})]})]})})]})}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-typescript",children:"[x, y] = [\n x * Math.cos(-rotate) -\n y * Math.sin(-rotate),\n x * Math.sin(-rotate) +\n y * Math.cos(-rotate),\n];\n"})}),"\n",(0,t.jsx)(s.admonition,{type:"note",children:(0,t.jsxs)(s.p,{children:["To match the behavior of Apophysis/",(0,t.jsx)(s.code,{children:"flam3"}),", we need to negate the rotation angle."]})}),"\n",(0,t.jsx)(s.h3,{id:"zoom",children:"Zoom"}),"\n",(0,t.jsxs)(s.p,{children:["This parameter does what the name implies; zoom in and out of the image. To do this, we multiply\nthe X and Y coordinates of each point by a zoom factor. For a zoom parameter ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsx)(s.mrow,{children:(0,t.jsx)(s.mi,{children:"z"})}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"z"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"0.4306em"}}),(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.04398em"},children:"z"})]})})]}),", the zoom factor\nwill be ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mtext,{children:"pow"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mi,{children:"z"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"\\text{pow}(2, z)"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mord text",children:(0,t.jsx)(s.span,{className:"mord",children:"pow"})}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord mathnormal",style:{marginRight:"0.04398em"},children:"z"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})})]}),"."]}),"\n",(0,t.jsxs)(s.p,{children:["For example, if the current point is ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"(1, 1)"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})})]}),", a zoom parameter of 1 means we actually plot ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"}),(0,t.jsx)(s.mo,{children:"\u22C5"}),(0,t.jsx)(s.mtext,{children:"pow"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"}),(0,t.jsx)(s.mo,{children:"="}),(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"(1, 1) \\cdot \\text{pow}(2, 1) = (2, 2)"})]})})}),(0,t.jsxs)(s.span,{className:"katex-html","aria-hidden":"true",children:[(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mclose",children:")"}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}}),(0,t.jsx)(s.span,{className:"mbin",children:"\u22C5"}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2222em"}})]}),(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mord text",children:(0,t.jsx)(s.span,{className:"mord",children:"pow"})}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mclose",children:")"}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2778em"}}),(0,t.jsx)(s.span,{className:"mrel",children:"="}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.2778em"}})]}),(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})]})]}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{children:"[x, y] = [\n x * Math.pow(2, zoom),\n y * Math.pow(2, zoom)\n];\n"})}),"\n",(0,t.jsx)(s.admonition,{type:"info",children:(0,t.jsxs)(s.p,{children:["In addition to scaling the image, renderers also ",(0,t.jsx)(s.a,{href:"https://github.com/scottdraves/flam3/blob/f8b6c782012e4d922ef2cc2f0c2686b612c32504/rect.c#L796-L797",children:"scale the image quality"}),"\nto compensate for the reduced display range."]})}),"\n",(0,t.jsx)(s.h3,{id:"scale",children:"Scale"}),"\n",(0,t.jsx)(s.p,{children:"Finally, we need to convert from fractal flame coordinates to individual pixels. The scale parameter defines\nhow many pixels are in one unit of the fractal flame coordinate system, which gives us a mapping from one system\nto the other."}),"\n",(0,t.jsxs)(s.p,{children:["If you open the ",(0,t.jsx)(s.a,{target:"_blank","data-noBrokenLinkCheck":!0,href:a(83335).Z+"",children:"reference parameters"})," in a text editor, you'll see the following:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-xml",children:'<flame name="final xform" size="600 600" center="0 0" scale="150">\n'})}),"\n",(0,t.jsx)(s.p,{children:"Here's what each element means:"}),"\n",(0,t.jsxs)(s.ul,{children:["\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:'size="600 600"'}),": The image should be 600 pixels wide and 600 pixels tall"]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:'center="0 0"'}),": The image is centered at the point ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"(0, 0)"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})})]})]}),"\n",(0,t.jsxs)(s.li,{children:[(0,t.jsx)(s.code,{children:'scale="150"'}),": The image has 150 pixels per unit"]}),"\n"]}),"\n",(0,t.jsxs)(s.p,{children:["Let's break it down. Dividing the image width (600) by the image scale (150) gives us a value of 4.\nThis means the image should be 4 units wide (same for the height). Because the center is at ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"("}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"0"}),(0,t.jsx)(s.mo,{stretchy:"false",children:")"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"(0, 0)"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"("}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"0"}),(0,t.jsx)(s.span,{className:"mclose",children:")"})]})})]}),",\nthe final image is effectively using the range ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[-2, 2]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]})," in fractal coordinates."]}),"\n",(0,t.jsx)(s.p,{children:"Now, to go from fractal coordinates to pixel coordinates we multiply by the scale,\nthen subtract half the image width and height:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-typescript",children:"[pixelX, pixelY] = [\n x * scale - imageWidth / 2,\n y * scale - imageHeight / 2\n]\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Scale and zoom have similar effects on images. If the reference parameters used ",(0,t.jsx)(s.code,{children:'scale="300"'}),",\nthe same 600 pixels would instead be looking at the range ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"1"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[-1, 1]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"1"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]})," in the fractal coordinate system.\nUsing ",(0,t.jsx)(s.code,{children:'zoom="1"'})," would accomplish the same result."]}),"\n",(0,t.jsxs)(s.p,{children:["However, this also demonstrates the biggest problem with using scale: it only controls the output image.\nFor example, if the output image changed to ",(0,t.jsx)(s.code,{children:'size="1200 1200"'})," and we kept ",(0,t.jsx)(s.code,{children:'scale="150"'}),", it would\nhave a display range of ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsx)(s.mn,{children:"4"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"4"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[-4, 4]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mord",children:"4"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"4"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]}),". There would be a lot of extra white space. Because the zoom parameter\nhas the same effect regardless of output image size, it is the preferred way to zoom in and out."]}),"\n",(0,t.jsxs)(s.admonition,{type:"info",children:[(0,t.jsx)(s.p,{children:"One final note about the camera controls: every step in this process (position, rotation, zoom, scale)\nis an affine transformation. And because affine transformations can be chained together, it's possible to\nexpress all the camera controls as a single transformation matrix. This is important for software optimization;\nrather than applying parameters step-by-step, we can apply all of them at once."}),(0,t.jsx)(s.p,{children:"They could also be implemented as part of the final transform, but in practice, it's helpful\nto control them separately."})]}),"\n",(0,t.jsx)(s.h2,{id:"camera",children:"Camera"}),"\n",(0,t.jsx)(s.p,{children:'With the individual steps defined, we can put together a more robust "camera" for viewing the fractal flame.'}),"\n","\n",(0,t.jsx)(r.Z,{language:"typescript",children:"export function camera(\n x: number,\n y: number,\n width: number,\n height: number,\n positionX: number,\n positionY: number,\n rotate: number,\n zoom: number,\n scale: number,\n): [number, number] {\n // Position, rotation, and zoom are\n // applied in IFS coordinates\n [x, y] = [\n (x - positionX),\n (y - positionY),\n ];\n\n [x, y] = [\n x * Math.cos(rotate) -\n y * Math.sin(rotate),\n x * Math.sin(rotate) +\n y * Math.cos(rotate),\n ];\n\n [x, y] = [\n x * Math.pow(2, zoom),\n y * Math.pow(2, zoom)\n ];\n\n // Scale transforms IFS coordinates\n // to pixel coordinates. Shift by half\n // the image width and height\n // to compensate for IFS coordinates\n // being symmetric around the origin\n return [\n Math.floor(x * scale + width / 2),\n Math.floor(y * scale + height / 2)\n ];\n}\n"}),"\n",(0,t.jsxs)(s.p,{children:["To demonstrate, this display has a 4:3 aspect ratio, removing the restriction of a square image.\nIn addition, the scale is automatically chosen so the image width covers the range ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"2"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[-2, 2]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"2"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]}),".\nBecause of the 4:3 aspect ratio, the image height now covers the range ",(0,t.jsxs)(s.span,{className:"katex",children:[(0,t.jsx)(s.span,{className:"katex-mathml",children:(0,t.jsx)(s.math,{xmlns:"http://www.w3.org/1998/Math/MathML",children:(0,t.jsxs)(s.semantics,{children:[(0,t.jsxs)(s.mrow,{children:[(0,t.jsx)(s.mo,{stretchy:"false",children:"["}),(0,t.jsx)(s.mo,{children:"\u2212"}),(0,t.jsx)(s.mn,{children:"1.5"}),(0,t.jsx)(s.mo,{separator:"true",children:","}),(0,t.jsx)(s.mn,{children:"1.5"}),(0,t.jsx)(s.mo,{stretchy:"false",children:"]"})]}),(0,t.jsx)(s.annotation,{encoding:"application/x-tex",children:"[-1.5, 1.5]"})]})})}),(0,t.jsx)(s.span,{className:"katex-html","aria-hidden":"true",children:(0,t.jsxs)(s.span,{className:"base",children:[(0,t.jsx)(s.span,{className:"strut",style:{height:"1em",verticalAlign:"-0.25em"}}),(0,t.jsx)(s.span,{className:"mopen",children:"["}),(0,t.jsx)(s.span,{className:"mord",children:"\u2212"}),(0,t.jsx)(s.span,{className:"mord",children:"1.5"}),(0,t.jsx)(s.span,{className:"mpunct",children:","}),(0,t.jsx)(s.span,{className:"mspace",style:{marginRight:"0.1667em"}}),(0,t.jsx)(s.span,{className:"mord",children:"1.5"}),(0,t.jsx)(s.span,{className:"mclose",children:"]"})]})})]}),"."]}),"\n","\n",(0,t.jsx)(i.ke,{name:"flame_camera",width:"95%",aspectRatio:"4/3",children:(0,t.jsx)(f,{})}),"\n",(0,t.jsx)(s.h2,{id:"summary",children:"Summary"}),"\n",(0,t.jsx)(s.p,{children:'The previous fractal images relied on assumptions about the output format to make sure they looked correct.\nNow, we have much more control. We can implement a 2D "camera" as a series of affine transformations, going from\nfractal flame coordinates to pixel coordinates.'}),"\n",(0,t.jsxs)(s.p,{children:["More recent fractal flame renderers like ",(0,t.jsx)(s.a,{href:"http://fractorium.com/",children:"Fractorium"})," can also operate in 3D,\nand have to implement a more complex camera system to handle the extra dimension."]}),"\n",(0,t.jsx)(s.p,{children:"But for this blog series, it's nice to achieve feature parity with the reference implementation."})]})}function b(e={}){let{wrapper:s}={...(0,l.a)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(v,{...e})}):v(e)}},83335:function(e,s,a){a.d(s,{Z:function(){return n}});let n=a.p+"assets/files/params-79404da2c6b544cbaf223f19db41fabf.flame"},5749:function(e,s,a){a.d(s,{Z:function(){return n}});let n=a.p+"assets/images/camera-controls-9acf6770c681d143ce91123adec4924c.png"},9976:function(e,s,a){a.d(s,{w:function(){return n}});function n(e,s){let a=3*Math.floor(s*(e.length/3));return[e[a],e[a+1],e[a+2]]}},2301:function(e,s,a){a.d(s,{R:function(){return n}});function n(e,s,a){return e*(1-a)+s*a}},17885:function(e,s,a){a.d(s,{g:function(){return n}});function n(e,s,a,n,t,l){let r=e*s,i=new ImageData(e,s);for(let e=0;e<r;e++){let s=Math.log10(l[e])/(1.5*l[e]),r=4*e,c=a[e]*s*255;i.data[r]=c;let m=n[e]*s*255;i.data[r+1]=m;let h=t[e]*s*255;i.data[r+2]=h;let o=l[e]*s*255;i.data[r+3]=o}return i}},88100:function(e,s,a){a.d(s,{ke:function(){return m},wn:function(){return r}});var n=a(85893),t=a(67294),l=a(79207);let r=(0,t.createContext)(null),i=e=>s=>{let a=document.createElement("a");a.download=`${e}.png`,a.href=s.target.toDataURL("image/png"),a.click()},c=e=>{let{name:s,style:a,children:c}=e,m=(0,t.useRef)(null),[h,o]=(0,t.useState)(0),[d,x]=(0,t.useState)(0);(0,t.useEffect)(()=>{m.current&&(o(m.current.offsetWidth),x(m.current.offsetHeight))},[m]);let p=(0,t.useRef)(null),[j,u]=(0,t.useState)(!1);(0,t.useEffect)(()=>{if(!p.current)return;let e=new IntersectionObserver(e=>{let[s]=e;s.isIntersecting&&u(!0)});return e.observe(p.current),()=>{p.current&&e.unobserve(p.current)}},[p.current]);let[f,g]=(0,t.useState)(null);(0,t.useEffect)(()=>{p.current&&f&&p.current.getContext("2d").putImageData(f[0],0,0)},[p,f]);let[N,y]=(0,t.useState)(null);(0,t.useEffect)(()=>{if(!j||!N)return;let e=N[0],s=e.next().value;s?(g([s]),y([e])):y(null)},[j,N]);let[w,v]=(0,t.useState)(null);(0,t.useEffect)(()=>{w&&y([w])},[w]);let b={ref:p,width:h,height:d,style:{filter:"dark"===(0,l.I)().colorMode?"invert(1)":""}};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("center",{children:(0,n.jsx)("div",{ref:m,style:a,children:h>0?(0,n.jsx)("canvas",{...b,onDoubleClick:i(s)}):null})}),(0,n.jsx)(r.Provider,{value:{width:h,height:d,setPainter:v},children:h>0?c:null})]})},m=e=>{let{name:s,width:a,aspectRatio:t,style:l,children:r}=e;return(0,n.jsx)("center",{children:(0,n.jsx)(c,{name:s,style:{width:a??"75%",aspectRatio:t??"1/1",...l},children:r})})}},25604:function(e,s,a){a.d(s,{N:()=>t,g:()=>l});var n=a("35149");let t=(e,s)=>(a,t)=>(function(e,s,a){let[n,t]=[0,0];for(let[l,r]of a){let[a,i]=r(e,s);n+=l*a,t+=l*i}return[n,t]})(...(0,n.z)(a,t,e),s),l=(e,s)=>(a,t)=>(0,n.z)(...s(a,t),e)},84896:function(e,s,a){function n(e,s,a){return[Math.floor((e+2)*a/4),Math.floor((s+2)*a/4)]}function t(e,s,a,n){return a*n*s+e*n}a.d(s,{j:function(){return t},n:function(){return n}})},36258:function(e,s,a){a.d(s,{e:function(){return t}});let n=()=>Math.random()>.5?0:Math.PI,t=(e,s)=>{let a=Math.sqrt(Math.sqrt(Math.pow(e,2)+Math.pow(s,2))),t=Math.atan2(e,s)/2+n();return[a*Math.cos(t),a*Math.sin(t)]}},25852:function(e,s,a){a.d(s,{G:function(){return n}});let n=(e,s)=>[e,s]},31224:function(e,s,a){a.d(s,{DG:function(){return F},DO:function(){return j},EW:function(){return v},L4:function(){return A},N3:function(){return p},Qt:function(){return f},SV:function(){return D},Ue:function(){return d},Ux:function(){return M},ZU:function(){return b},aI:function(){return h},d3:function(){return w},fm:function(){return m},iD:function(){return E},kO:function(){return g},qR:function(){return y},rK:function(){return x},sB:function(){return B},v$:function(){return u},xN:function(){return o},y7:function(){return C},yV:function(){return N}});var n=a(25852),t=a(36258),l=a(70766),r=a(44618),i=a(25604);let c={a:1,b:0,c:0,d:0,e:1,f:0},m={a:1.09358,b:2.13048,c:2.54127,d:2.37267},h=.56453495,o={a:-1.381068,b:-1.381068,c:0,d:1.381068,e:-1.381068,f:0},d=c,x=[[1,t.e]],p=0,j=.013135,u={a:.031393,b:.031367,c:0,d:-.031367,e:.031393,f:0},f={a:1,b:0,c:.24,d:0,e:1,f:.27},g=[[1,n.G],[1,(0,l.X)(u)]],N=.844,y=.42233,w={a:1.51523,b:-3.048677,c:.724135,d:.740356,e:-1.455964,f:-.362059},v=c,b=[[1,(0,r.i)(m)]],E=.349,M={a:2,b:0,c:0,d:0,e:2,f:0},A=c,k=[[1,t.e]],B=0,C=[[h,(0,i.g)(d,(0,i.N)(o,x))],[j,(0,i.g)(f,(0,i.N)(u,g))],[y,(0,i.g)(v,(0,i.N)(w,b))]],D=(0,i.g)(A,(0,i.N)(M,k)),F=(function(e){let s=[];for(let a=0;a<e.length;a+=2)s.push(parseInt(e.substring(a,a+2),16));return s})("3130323635383B3A3D403F424644484B494D504E525653585B585D605D626562686B676D706C737571787B767D807B838580888A858D908A93958F989A949DA099A3A59EA8AAA3ADAFA8B3B5ADB8BAB2BEBFB7C3C5BCC8CAC1CECFC6D3D4CBD8DAD0DEDFD5E3DFD2E0DFCEDDE0CBDAE0C8D7E0C4D3E0C1D0E1BECDE1BBCAE1B7C7E1B4C4E1B1C1E2ADBEE2AABAE2A7B7E2A3B4E2A0B1E39DAEE399ABE396A8E393A5E490A1E48C9EE4899BE48698E48295E57F92E57C8FE5788CE57589E57285E66E82E66B7FE6687CE66479E76176E75E73E75B70E7576CE75469E85166E84D63E84A60E4495EE0485CDC475BD84659D44557D04455CB4353C74252C34150BF404EBB3F4CB73E4BB33D49AF3C47AB3B45A73A43A339429F38409B373E97363C92353A8E34398A33378632358231337E30327A2F30762E2E722D2C6E2C2A6A2B29662A276229255E282359272155262051251E4D241C49231A4522194121173D20153C1F153A1F14391E14381E14361D14351C13341C13321B13311B132F1A122E19122D19122B18122A181129171127161126161125151023151022141021140F1F130F1E120F1C120F1B110E1A110E18100E170F0E160F0D140E0D130E0D120D0D100C0C0F0C0C0E0B0C0C0B0C0B0A0B09090B08090B07080B05080A04070A0606090804090A03088C46728A457087446D85436B8243698042667D41647B4061793F5F763E5D743D5A713D586F3C566C3B536A3A5168394F65384C63374A6037485E36455B354359344057333E54323C5231394F31374D30354A2F32482E30462D2E432C2B412B293E2B273C2A2439292237281F35271D32261B3025182D25162B241428231126220F25210F24210E23200E221F0E221E0D211E0D201D0D1F1C0D1E1B0C1D1B0C1C1A0C1B190B1B180B1A180B19170A18160A17150A1615091514091413091413081312081211081110081010070F0F070E0E070D0D060C0D060C0C060B0B050A0A05090A050809040708040607040507040506030405030304030204020103020608070C0D0D1112121617171B1C1D2121222626272B2B2D").map(e=>e/255)},44618:function(e,s,a){a.d(s,{i:function(){return n}});let n=e=>{let{a:s,b:a,c:n,d:t}=e;return(e,l)=>[Math.sin(s*l)-Math.cos(a*e),Math.sin(n*e)-Math.cos(t*l)]}},70766:function(e,s,a){a.d(s,{X:function(){return n}});let n=e=>{let{c:s,f:a}=e;return(e,n)=>[e+s*Math.sin(Math.tan(3*n)),n+a*Math.sin(Math.tan(3*e))]}},72373:function(e,s,a){a.d(s,{J:function(){return n}});function n(){return 2*Math.random()-1}},86539:function(e,s,a){a.d(s,{m:function(){return n}});function n(e){let s=Math.random()*e.reduce((e,s)=>{let[a,n]=s;return e+a},0);for(let a of e.entries()){let[e,n]=a,[t,l]=n;if(s<t)return[e,l];s-=t}let a=e.length-1;return[a,e[a][1]]}},35149:function(e,s,a){a.d(s,{z:function(){return n}});function n(e,s,a){return[e*a.a+s*a.b+a.c,e*a.d+s*a.e+a.f]}},13109:function(e,s,a){a.d(s,{Z:()=>y});var n=a("85893");a("67294");var t=a("67026"),l=a("6735"),r=a("34550"),i=a("7670"),c=a("87262"),m=a("44634"),h=a("19358"),o=a("3829"),d=a("15514");function x(e){let{children:s}=e;return(0,n.jsx)("div",{className:(0,t.Z)("playgroundHeader_qwyd"),children:s})}function p(){return(0,n.jsx)("div",{children:"Loading..."})}function j(){return(0,n.jsx)(m.Z,{fallback:(0,n.jsx)(p,{}),children:()=>(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(d.Z,{fallback:e=>(0,n.jsx)(h.Ac,{...e}),children:(0,n.jsx)(r.i5,{})}),(0,n.jsx)(r.IF,{})]})})}function u(){return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(x,{children:(0,n.jsx)(i.Z,{id:"theme.Playground.result",description:"The result label of the live codeblocks",children:"Result"})}),(0,n.jsx)("div",{className:"playgroundPreview_bb8I",children:(0,n.jsx)(j,{})})]})}function f(){let e=(0,l.Z)();return(0,n.jsx)(r.uz,{className:"playgroundEditor_PvJ1"},String(e))}function g(){return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(x,{children:(0,n.jsx)(i.Z,{id:"theme.Playground.liveEditor",description:"The live editor label of the live codeblocks",children:"Live Editor"})}),(0,n.jsx)(f,{})]})}let N=e=>`${e};`;function y(e){let{children:s,transformCode:a,...t}=e,{siteConfig:{themeConfig:l}}=(0,c.Z)(),{liveCodeBlock:{playgroundPosition:i}}=l,m=(0,o.p)(),h=t.metastring?.includes("noInline")??!1;return(0,n.jsx)("div",{className:"playgroundContainer_TGbA",children:(0,n.jsx)(r.nu,{code:s?.replace(/\n$/,""),noInline:h,transformCode:a??N,theme:m,...t,children:"top"===i?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(u,{}),(0,n.jsx)(g,{})]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(g,{}),(0,n.jsx)(u,{})]})})})}},33671:function(e){e.exports=JSON.parse('{"permalink":"/2025/03/playing-with-fire-camera","source":"@site/blog/2024-11-15-playing-with-fire/4-camera/index.mdx","title":"Playing with fire: The camera","description":"Something that bugged me while writing the first three articles on fractal flames were the constraints on","date":"2025-03-10T12:00:00.000Z","tags":[],"readingTime":5.515,"hasTruncateMarker":true,"authors":[{"name":"Bradlee Speice","socials":{"github":"https://github.com/bspeice"},"key":"bspeice","page":null}],"frontMatter":{"slug":"2025/03/playing-with-fire-camera","title":"Playing with fire: The camera","date":"2025-03-10T12:00:00.000Z","authors":["bspeice"],"tags":[]},"unlisted":false,"lastUpdatedAt":1741655608000,"nextItem":{"title":"Playing with fire: Tone mapping and color","permalink":"/2024/11/playing-with-fire-log-density"}}')}}]); |