Roman Cortes brought in red roses written in JavaScript scripts. Rose made with code is the best Valentine's Day gift given to your girlfriend by awesome programmers! (Tip: The viewing effect and speed will be very different under different browsers)
The image is generated by code, and the user can refresh the page and repeat the presentation process of watching the rose.
The implementation code of 3D rose is as follows:
The code copy is as follows: with(m=Math)C=cos,S=sin,P=pow,R=random;c.width=c.height=f=500;h=-250;function p(a,b,c){if(c>60)return[S(a*7)*(13+5/(.2+P(b*4,4)))-S(b)*50,b*f+50,625+C(a*7)*(13+5/(.2+P(b*4,4)))+b*400,a*1-b/2,a];A=a*2-1;B=b*2-1;if(A*A+B*B<1){if(c>37){n=(j=c&1) ?6:4;o=.5/(a+.01)+C(b*125)*3-a*300;w=b*h;return[o*C(n)+w*S(n)+j*610-390,o*S(n)-w*C(n)+550-j*350,1180+C(B+A)*99-j*300,.4-a*.1+P(1-B*B,-h*6)*.15-a*b*.4+C(a+b)/5+P(C(( o*(a+1)+(B>0?w:-w))/25),30)*.1*(1-B*B),o/1e3+.7-o*w*3e-6]}if(c>32){c=c*1.16-.15;o=a*45-20;w=b*b*h;z=o*S(c)+w*C(c)+620;return[o*C(c)-w*S(c),28+C(B*.5)*99-b*b*b*60-z/ 2-h,z,(b*b*.3+P((1-(A*A)),7)*.15+.3)*b,b*.7]}o=A*(2-b)*(80-c*2);w=99-C(A)*120-C(b)*(-hc*4.9)+C(P(1-b,7))*50+c*2;z=o*S(c)+w*C(c)+700; return[o*C(c)-w*S(c),B*99-C(P(b, 7))*50-c/3-z/1.35+450,z,(1-b/1.2)*.9+a*.1, P((1-b),20)/4+.05]}}setInterval('for(i=0;i<1e4;i++)if(s=p(R(),R(),i%46/.74)){z=s[2];x=~~(s[0]*f/zh);y=~~(s[1]*f/zh);if(!m[q=y*f+x]|m[q]>z)m[q]=z,a.fillStyle="rgb("+~(s[3]*h)+","+~(s[4]*h)+","+~(s[3]*s[3]*-80)+")",a.fillRect(x,y,1,1)}',0)
Of course, interested people can understand the following implementation process and related theories:
The presentation effect of this three-dimensional code rose uses the Monte Carlo method, and the creator highly praised the Monte Carlo method. He said that the Monte Carlo method is an "incredibly powerful tool" in terms of functional optimization and sampling. For the Monte Carlo method, please refer to: Monte Carlo method.
Specific operations:
Appearance sampling rendering effect drawing
I used multiple different shape diagrams to form this code rose. A total of 31 shapes were used: 24 petals, 4 sepals, 2 leaves and 1 flower stem, and each shape diagram was depicted in code.
First, define a sampling range:
function surface(a, b) { // I'm using a and b as parameters ranged from 0 to 1.return {x: a*50,y: b*50};// this surface will be a square of 50x50 units of size}Then, write the shape delineation code:
var canvas = document.body.appendChild(document.createElement("canvas")),context = canvas.getContext("2d"),a, b, position;// Now I'm going to sample the surface at .1 intervals for a and b parameters:for (a = 0; a < 1; a += .1) {for (b = 0; b < 1; b += .1) {position = surface(a, b);context.fillRect(position.x, position.y, 1, 1);}}Now, try a more dense sampling interval:
As you can see now, because the sampling intervals are getting denser and closer, the points are getting closer and closer to the highest density, the distance between adjacent points is less than one pixel, and the interval cannot be seen by the naked eye (see 0.01). In order not to cause too much visual difference, the sampling interval is further reduced. At this time, the drawing area has been filled (comparison results are 0.01 and 0.001).
Next, I use this formula to draw a circle: (X-X0)^2 + (Y-Y0)^2 <radius^2, where (X0, Y0) is the center of the circle:
function surface(a, b) {var x = a * 100,y = b * 100,radius = 50,x0 = 50,y0 = 50;if ((x - x0) * (x - x0) + (y - y0) * (y - y0) < radius * radius) {// inside the circlereturn {x: x,y: y};} else {// outside the circlereturn null;}}To prevent overflow, a sampling condition must be added:
if (position = surface(a, b)) {context.fillRect(position.x, position.y, 1, 1);}There are different ways to define a circle, some of which do not require rejection of sampling. I don't have to use which one to define a circle, so I use another method to define a circle:
function surface(a, b) {// Circle using polar coordinatesvar angle = a * Math.PI * 2,radius = 50,x0 = 50,y0 = 50;return {x: Math.cos(angle) * radius * b + x0,y: Math.sin(angle) * radius * b + y0};}(This method requires dense sampling for filling compared to the previous method.)
OK, now let the circle deform to make it look more like a petal:
function surface(a, b) {var x = a * 100,y = b * 100,radius = 50,x0 = 50,y0 = 50;if ((x - x0) * (x - x0) + (y - y0) * (y - y0) < radius * radius) {return {x: x,y: y * (1 + b) / 2 // deformation};} else {return null;}}This looks like a rose petal. Here you can also try to modify some function values, and many interesting shapes will appear.
Next, add color to it:
function surface(a, b) {var x = a * 100,y = b * 100,radius = 50,x0 = 50,y0 = 50;if ((x - x0) * (x - x0) + (y - y0) * (y - y0) < radius * radius) {return {x: x,y: y * (1 + b) / 2,r: 100 + Math.floor((1 - b) * 155), // this will add a gradientg: 50,b: 50};} else {return null;}}for (a = 0; a < 1; a += .01) {for (b = 0; b < 1; b += .001) {if (point = surface(a, b)) {context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";context.fillRect(point.x, point.y, 1, 1);}}}A colored petal appears.
3D surface and perspective projection
Defining a three-dimensional surface is simple, for example, to define a tubular object:
function surface(a, b) {var angle = a * Math.PI * 2,radius = 100,length = 400;return {x: Math.cos(angle) * radius,y: Math.sin(angle) * radius,z: b * length - length / 2, // by subtracting length/2 I have centered the tube at (0, 0, 0)r: 0,g: Math.floor(b * 255),b: 0};}Then add a projection perspective. First we need to define a camera:
As shown above, place the camera in (0, 0, Z) position and the canvas in the X/Y plane. The sampling points projected onto the canvas are:
var pX, pY, // projected on canvas x and y coordinatesperspective = 350,halfHeight = canvas.height / 2,halfWidth = canvas.width / 2,cameraZ = -700;for (a = 0; a < 1; a += .001) {for (b = 0; b < 1; b += .01) {if (point = surface(a, b)) {pX = (point.x * perspective) / (point.z - cameraZ) + halfWidth;pY = (point.y * perspective) / (point.z - cameraZ) + halfHeight;context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";context.fillRect(pX, pY, 1, 1);}}}The effect is:
z-buffer
z-buffer is a very common technique in computer graphics. When shading objects, the "hidden surface elimination" work is performed so that the part behind the hidden objects will not be displayed.
The picture above is a rose processed with z-buffer technology. (You can see that it already has three-dimensionality)
The code is as follows:
var zBuffer = [],zBufferIndex;for (a = 0; a < 1; a += .001) {for (b = 0; b < 1; b += .01) {if (point = surface(a, b)) {pX = Math.floor((point.x * perspective) / (point.z - cameraZ) + halfWidth);pY = Math.floor((point.y * perspective) / (point.z - cameraZ) + halfHeight);zBufferIndex = pY * canvas.width + pX;if ((typeof zBuffer[zBufferIndex] === "undefined") || (point.z < zBuffer[zBufferIndex])) {zBuffer[zBufferIndex] = point.z;context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";context.fillRect(pX, pY, 1, 1);}}}}Rotate
You can use any vector rotation method. In the creation of the code rose, I use Euler rotation. Now rotate the previously written tubular object to rotate about the Y axis:
function surface(a, b) {var angle = a * Math.PI * 2,radius = 100,length = 400,x = Math.cos(angle) * radius,y = Math.sin(angle) * radius,z = b * length - length / 2,yAxisRotationAngle = -.4, // in radians!rotatedX = x * Math.cos(yAxisRotationAngle) + z * Math.sin(yAxisRotationAngle),rotatedZ = x * -Math.sin(yAxisRotationAngle) + z * Math.cos(yAxisRotationAngle);return {x: rotatedX,y: y,z: rotatedZ,r: 0,g: Math.floor(b * 255),b: 0};}Effect:
Monte Carlo Method
Regarding the sampling time, too large and too small will cause extremely poor visual experience, so a reasonable sampling interval needs to be set, and the Monte Carlo method is used here.
var i;window.setInterval(function () {for (i = 0; i < 10000; i++) {if (point = surface(Math.random(), Math.random())) {pX = Math.floor((point.x * perspective) / (point.z - cameraZ) + halfWidth);pY = Math.floor((point.y * perspective) / (point.z - cameraZ) + halfHeight);zBufferIndex = pY * canvas.width + pX;if ((typeof zBuffer[zBufferIndex] === "undefined") || (point.z < zBuffer[zBufferIndex])) {zBuffer[zBufferIndex] = point.z;context.fillStyle = "rgb(" + point.r + "," + point.g + "," + point.b + ")";context.fillRect(pX, pY, 1, 1);}}}}, 0);Set a and b as random parameters and complete surface filling with sufficient sampling. I draw 10000 points each time and wait for the screen to complete the update.
It should also be noted that if a random number is incorrect, the surface filling effect will be incorrect. In some browsers, the execution of Math.random is linear, which may lead to errors in surface filling effects. At this time, you have to use something like Mersenne Twister (a random number algorithm) to perform high-quality PRNG sampling to avoid errors.
Finish
In order for each part of the rose to complete and render at the same time, a feature needs to be added to set a parameter for each part to return the value for synchronization. And use a segment function to represent the various parts of the rose. For example, in the petal part, I use rotation and deformation to create them.
Although the surface sampling method is one of the most famous and oldest methods for creating three-dimensional graphics, this method of adding Monte Carlo and z-buffer to surface sampling is not common. This may not be very creative for real-life scenario production, but its simple code implementation and small size are still satisfactory.
Hopefully this article will inspire computer graphics enthusiasts to try different presentation methods and have fun from it. (Roman Cortes)
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.