$\left( \begin{matrix} x \\ y \\ z \end{matrix} \right) = \left( \begin{matrix} (R_2 + R_1 \cos \theta) (\cos B \cos \phi + \sin A \sin B \sin \phi) - R_1 \cos A \sin B \sin \theta \\ (R_2 + R_1 \cos \theta) (\cos \phi \sin B - \cos B \sin A \sin \phi) + R_1 \cos A \cos B \sin \theta \\ \cos A (R_2 + R_1 \cos \theta) \sin \phi + R_1 \sin A \sin \theta \end{matrix} \right) = \left( \begin{matrix} C_x(\cos B \cos \phi + \sin A \sin B \sin \phi) - C_y \cos A \sin B \\ C_x(\cos \phi \sin B - \cos B \sin A \sin \phi) + C_y \cos A \cos B \\ C_x \cos A \sin \phi + C_y \sin A \end{matrix} \right)$
(function() { let_onload = () => { let pretag = document.getElementById('d'); let canvastag = document.getElementById('canvasdonut'); let canvascube = document.getElementById('canvascube');
let tmr1 = undefined; let tmr2 = undefined;
// This is copied, pasted, reformatted, and ported directly from my original // donut.c code let asciiframe= ((A,B) =>() => { let b=[]; let z=[]; A += 0.07; B += 0.03; let cA=Math.cos(A), sA=Math.sin(A), cB=Math.cos(B), sB=Math.sin(B); for(let k=0;k<1760;k++) { b[k]= k%80 == 79 ? "\n" : " "; z[k]= 0; } for(let j=0;j<6.28;j+=0.07) { // j <=> theta let ct=Math.cos(j),st=Math.sin(j); for(i=0;i<6.28;i+=0.02) { // i <=> phi let sp=Math.sin(i),cp=Math.cos(i), h=ct+2, // R1 + R2*cos(theta) D=1/(sp*h*sA+st*cA+5), // this is 1/z t=sp*h*cA-st*sA; // this is a clever factoring of some of the terms in x' and y'
// This is a reimplementation according to my math derivation on the page let canvasframe = ((A,B) =>(R1,R2,K1,K2,rA,rB) => { let ctx = canvastag.getContext('2d'); ctx.fillStyle='#000'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
A += rA*(Math.random()+0.5); B += rB*(Math.random()+0.5); // precompute cosines and sines of A, B, theta, phi, same as before let cA=Math.cos(A), sA=Math.sin(A), cB=Math.cos(B), sB=Math.sin(B); for(let j=0;j<6.28;j+=0.3) { // j <=> theta let ct=Math.cos(j),st=Math.sin(j); // cosine theta, sine theta for(i=0;i<6.28;i+=0.1) { // i <=> phi let sp=Math.sin(i),cp=Math.cos(i); // cosine phi, sine phi let ox = R2 + R1*ct, // object x, y = (R2,0,0) + (R1 cos theta, R1 sin theta, 0) oy = R1*st;
let x = ox*(cB*cp + sA*sB*sp) - oy*cA*sB; // final 3D x coordinate let y = ox*(sB*cp - sA*cB*sp) + oy*cA*cB; // final 3D y let ooz = 1/(K2 + cA*ox*sp + sA*oy); // one over z let xp=(150+K1*ooz*x); // x' = screen space coordinate, translated and scaled to fit our 320x240 canvas element let yp=(120-K1*ooz*y); // y' (it's negative here because in our output, positive y goes down but in our 3D space, positive y goes up) // luminance, scaled back to 0 to 1 let L=0.7*(cp*ct*sB - cA*ct*sp - sA*st + cB*(cA*st - ct*sA*sp)); if(L > 0) { ctx.fillStyle = `rgba(255,255,255,${L}`; ctx.fillRect(xp, yp, 10*L, 10*L); } } } })(Math.random()*Math.PI,Math.random()*Math.PI); window.anim2 = () => { if(tmr2 === undefined) { tmr2 = setInterval(() =>canvasframe(1,2,150,5,0.07,0.03), 50); } else { clearInterval(tmr2); tmr2 = undefined; } }; // Cube let canvasframe2 = ((A,B) =>(W,K1,K2,rA,rB,sX,sY) => { let ctx = canvascube.getContext('2d'); ctx.fillStyle='#000'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
A += rA*(Math.random()+0.5); B += rB*(Math.random()+0.5); // precompute cosines and sines of A, B, theta, phi, same as before let cA=Math.cos(A), sA=Math.sin(A), cB=Math.cos(B), sB=Math.sin(B); for(let j=-W/2;j<W/2;j+=0.2) { // j <=> theta for(i=-W/2;i<W/2;i+=0.2) { // i <=> phi for(z=-W/2;z<W/2;z+=0.2){ // (j,i,z) let x = j*cB - z*sA*sB - i*cA*sB; // final 3D x coordinate let y = j*sB + z*cB*sA + i*cA*cB; // final 3D y let ooz = K1/(K2 - z*cA + i*sA); // one over z let xp=(sX/2+ooz*x); // x' = screen space coordinate, translated and scaled to fit our 320x240 canvas element let yp=(sY/2-ooz*y); // y' (it's negative here because in our output, positive y goes down but in our 3D space, positive y goes up) // luminance, scaled back to 0 to 1 let L=(y + W/2 + (z*cA - i*sA)+W/2)/(2*W); if(L > 0) { ctx.fillStyle = `rgba(255,255,255,${L}`; ctx.fillRect(xp, yp, 10*L, 10*L); } } } } })(Math.random()*Math.PI,Math.random()*Math.PI);
float c=1, s=0; // c for cos, s for sin for (int i = 0; i < 314; i++) { // 314 * .02 ~= 2π // (use c, s in code) float newc = 0.9998*c - 0.019998666*s; s = 0.019998666*c + 0.9998*s; c = newc; }
$\begin{bmatrix} c’ \\ s’ \end{bmatrix} = \frac{1}{\cos \theta}\begin{bmatrix} 1 & -\tan \theta \\ \tan \theta & 1 \end{bmatrix} \begin{bmatrix} c \\ s \end{bmatrix}$
cos足够接近1, 我们甚至可以省略它,直接只用tan的部分加上牛顿切
其中t 是 tan, 宏编程
1 2 3 4 5 6 7
#define R(t,x,y) \ f = x; \ x -= t*y; \ y += t*f; \ f = (3-x*x-y*y)/2; \ x *= f; \ y *= f;
去掉float
We can use exactly the same ideas with integer fixed-point arithmetic, and not use any float math whatsoever. I’ve redone all the math with 10-bit precision and produced the following C code which runs well on embedded devices which can do 32-bit multiplications and have ~4k of available RAM: