围绕轴问题旋转画布

Rotating canvas about axis problems(围绕轴问题旋转画布)

本文介绍了围绕轴问题旋转画布的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 canvas 3d 绘制一个 3d 图形,我可以在其中绘制 (1,5,4)、(-8,6,-2) 等点.所以我能够绘制所有正面和负 x、y 和 z 轴.我也使用箭头键有旋转效果.旋转说明:z 轴从屏幕中心向外延伸.

I am using canvas 3d to draw a 3d graph in which i can plot points such as (1,5,4), (-8,6,-2) etc.So i am able to draw in all positive and negative x,y and z axis.I also have rotation effect by using arrow keys. Instructions for rotation: The z-axis extends out from the center of the screen.

要绕 x 轴旋转,请按向上/向下箭头键.要绕 y 轴旋转,请按左/右箭头键.要绕 z 轴旋转,请按 ctrl+left/ctrl+down 箭头键.

To rotate about the x-axis, press the up/down arrow keys. To rotate about the y-axis, press the left/right arrow keys. To rotate about the z-axis, press the ctrl+left/ctrl+down arrow keys.

我可以通过在我提供的文本字段中指定点来绘制点.现在的问题是,例如,如果我 plot(5,5,2) 它将正确绘制.但是如果我先旋转 x 轴然后旋转 y 轴,那么点将被正确绘制.如果我先旋转 y 轴然后旋转 x 轴,就会出现问题.该点将被错误地绘制.找到我遇到的问题的简单方法:如果您继续重复绘制同一点,这可以很容易地找出.该点应绘制在同一点上方,以便只有一个点可见.但在我的情况下,同一点(例如(5,5,2)旋转时在画布的不同位置绘制.只有当我先旋转 y 轴然后旋转 x 轴或先旋转 z 轴然后旋转 y 轴时才会出现这个问题.那么我在编码中犯了什么错误.我是这个画布 3d 和 java 脚本的新手.所以请帮忙.

I can plot the point by specifying points in the text field i provided. Now the problem is that for example if i plot(5,5,2) it will plot properly.But if i rotate x axis first and then y axis then point will be plotted properly. The problem comes if i rotate y-axis first and then x-axis.the point will be wrongly plotted. Easy method to find the problem i encountered: This can be easily find out if you go on plotting the same point repeatedly.The point should be plotted above the same point so that only single point is visible.But in my case the same point( for ex(5,5,2) is drawn at different place in canvas while rotating.This problem only comes if i rotate y-axis first and then x-axis or if i rotate z axis first and then y-axis. So what is the mistake i have done in coding.I am new to this canvas 3d and java script.So please help.

<html>

 <head>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<title>Canvas Surface Rotation</title>

<style>

  body {

    text-align: center;

  }



  canvas {

    border: 1px solid black;

  }

</style>

<script>  

var p1;
var p2;
var p3;
var p4;
var p5;
var p6;
var xangle=0;
var yangle=0;
var zangle=0;
  var constants = {

    canvasWidth: 600, // In pixels.

    canvasHeight: 600, // In pixels.

    leftArrow: 37,

    upArrow: 38,

    rightArrow: 39,

    downArrow: 40,

    xMin: -10, // These four max/min values define a square on the xy-plane that the surface will be plotted over.

    xMax: 10,

    yMin: -10,

    yMax: 10, 

    xDelta: 0.06, // Make smaller for more surface points. 

    yDelta: 0.06, // Make smaller for more surface points. 

    colorMap: ["#000080"], // There are eleven possible "vertical" color values for the surface, based on the last row of http://www.cs.siena.edu/~lederman/truck/AdvanceDesignTrucks/html_color_chart.gif

    pointWidth: 2, // The size of a rendered surface point (i.e., rectangle width and height) in pixels.

    dTheta: 0.05, // The angle delta, in radians, by which to rotate the surface per key press.

    surfaceScale: 24 // An empirically derived constant that makes the surface a good size for the given canvas size.

  };



  // These are constants too but I've removed them from the above constants literal to ease typing and improve clarity.

  var X = 0;

  var Y = 1;

  var Z = 2;



  // -----------------------------------------------------------------------------------------------------  



  var controlKeyPressed = false; // Shared between processKeyDown() and processKeyUp().

  var surface = new Surface(); // A set of points (in vector format) representing the surface.



  // -----------------------------------------------------------------------------------------------------



  function point(x, y, z)

  /*

    Given a (x, y, z) surface point, returns the 3 x 1 vector form of the point.

  */

  {       

    return [x, y, z]; // Return a 3 x 1 vector representing a traditional (x, y, z) surface point. This vector form eases matrix multiplication.

  }



  // -----------------------------------------------------------------------------------------------------



  function Surface()

  /*

    A surface is a list of (x, y, z) points, in 3 x 1 vector format. This is a constructor function.

  */

  {

    this.points = [];
    // An array of surface points in vector format. That is, each element of this array is a 3 x 1 array, as in [ [x1, y1, z1], [x2, y2, z2], [x3, y3, z3], ... ]

  }



  // -----------------------------------------------------------------------------------------------------  



  Surface.prototype.equation = function(x, y)

  /*

    Given the point (x, y), returns the associated z-coordinate based on the provided surface equation, of the form z = f(x, y).

  */

  {

    var d = Math.sqrt(x*x + y*y); // The distance d of the xy-point from the z-axis.



    return 4*(Math.sin(d) / d); // Return the z-coordinate for the point (x, y, z). 

  }



  // -----------------------------------------------------------------------------------------------------  



  Surface.prototype.generate = function()

  /*

    Creates a list of (x, y, z) points (in 3 x 1 vector format) representing the surface.

  */

  {

    var i = 0;



    for (var x = constants.xMin; x <= constants.xMax; x += constants.xDelta)

    {

      for (var y = constants.yMin; y <= constants.yMax; y += constants.yDelta)

      {

        this.points[i] = point(x, y, this.equation(x, y)); // Store a surface point (in vector format) into the list of surface points.              

        ++i;

      }

    }

  }



  // -----------------------------------------------------------------------------------------------------



  Surface.prototype.color = function()

  /*

    The color of a surface point is a function of its z-coordinate height.

  */

  {

    var z; // The z-coordinate for a given surface point (x, y, z).



    this.zMin = this.zMax = this.points[0][Z]; // A starting value. Note that zMin and zMax are custom properties that could possibly be useful if this code is extended later.

    for (var i = 0; i < this.points.length; i++)

    {            

      z = this.points[i][Z];

      if (z < this.zMin) { this.zMin = z; }

      if (z > this.zMax) { this.zMax = z; }

    }   



    var zDelta = Math.abs(this.zMax - this.zMin) / constants.colorMap.length; 



    for (var i = 0; i < this.points.length; i++)

    {

      this.points[i].color = constants.colorMap[ Math.floor( (this.points[i][Z]-this.zMin)/zDelta ) ];

    }



    /* Note that the prior FOR loop is functionally equivalent to the follow (much less elegant) loop:       

    for (var i = 0; i < this.points.length; i++)

    {

      if (this.points[i][Z] <= this.zMin + zDelta) {this.points[i].color = "#060";}

      else if (this.points[i][Z] <= this.zMin + 2*zDelta) {this.points[i].color = "#090";}

      else if (this.points[i][Z] <= this.zMin + 3*zDelta) {this.points[i].color = "#0C0";}

      else if (this.points[i][Z] <= this.zMin + 4*zDelta) {this.points[i].color = "#0F0";}

      else if (this.points[i][Z] <= this.zMin + 5*zDelta) {this.points[i].color = "#9F0";}

      else if (this.points[i][Z] <= this.zMin + 6*zDelta) {this.points[i].color = "#9C0";}

      else if (this.points[i][Z] <= this.zMin + 7*zDelta) {this.points[i].color = "#990";}

      else if (this.points[i][Z] <= this.zMin + 8*zDelta) {this.points[i].color = "#960";}

      else if (this.points[i][Z] <= this.zMin + 9*zDelta) {this.points[i].color = "#930";}

      else if (this.points[i][Z] <= this.zMin + 10*zDelta) {this.points[i].color = "#900";}          

      else {this.points[i].color = "#C00";}

    }

    */

  }



  // -----------------------------------------------------------------------------------------------------

  function update(){
document.querySelector("#xa").innerHTML = xangle;
document.querySelector("#ya").innerHTML = yangle;
document.querySelector("#za").innerHTML = zangle;
}

  function appendCanvasElement()

  /*

    Creates and then appends the "myCanvas" canvas element to the DOM.

  */

  {

    var canvasElement = document.createElement('canvas');



    canvasElement.width = constants.canvasWidth;

    canvasElement.height = constants.canvasHeight;

    canvasElement.id = "myCanvas";



    canvasElement.getContext('2d').translate(constants.canvasWidth/2, constants.canvasHeight/2); // Translate the surface's origin to the center of the canvas.



    document.body.appendChild(canvasElement); // Make the canvas element a child of the body element.

  }



  //------------------------------------------------------------------------------------------------------

  Surface.prototype.sortByZIndex = function(A, B) 

  {

    return A[Z] - B[Z]; // Determines if point A is behind, in front of, or at the same level as point B (with respect to the z-axis).

  }



  // -----------------------------------------------------------------------------------------------------



  Surface.prototype.draw = function()

  {

    var myCanvas = document.getElementById("myCanvas"); // Required for Firefox.

    var ctx = myCanvas.getContext("2d");
    var res;
    var xm;


   // this.points = surface.points.sort(surface.sortByZIndex); // Sort the set of points based on relative z-axis position. If the points are visibly small, you can sort of get away with removing this step.


    for (var i = 0; i < this.points.length; i++)

    {

      ctx.fillStyle = this.points[i].color; 

      ctx.fillRect(this.points[i][X] * constants.surfaceScale, this.points[i][Y] * constants.surfaceScale, constants.pointWidth, constants.pointWidth);


    }    

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.font="12px Arial";
ctx.fillStyle = "#000000";
ctx.fillText("X",this.points[p1][X] * constants.surfaceScale, this.points[p1][Y] * constants.surfaceScale);
var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("Y",this.points[p2][X] * constants.surfaceScale, this.points[p2][Y] * constants.surfaceScale);
var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("Z",this.points[p3][X] * constants.surfaceScale, this.points[p3][Y] * constants.surfaceScale);

var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("-Y",this.points[p4][X] * constants.surfaceScale, this.points[p4][Y] * constants.surfaceScale);

var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("-Z",this.points[p5][X] * constants.surfaceScale, this.points[p5][Y] * constants.surfaceScale);

var c=document.getElementById("myCanvas");
var ctx1=c.getContext("2d");
ctx1.font="12px Arial";
ctx1.fillText("-X",this.points[p6][X] * constants.surfaceScale, this.points[p6][Y] * constants.surfaceScale);


  }



  // -----------------------------------------------------------------------------------------------------



  Surface.prototype.multi = function(R)

  /*

    Assumes that R is a 3 x 3 matrix and that this.points (i.e., P) is a 3 x n matrix. This method performs P = R * P.

  */

  {

    var Px = 0, Py = 0, Pz = 0; // Variables to hold temporary results.

    var P = this.points; // P is a pointer to the set of surface points (i.e., the set of 3 x 1 vectors).

    var sum; // The sum for each row/column matrix product.



    for (var V = 0; V < P.length; V++) // For all 3 x 1 vectors in the point list.

    {

      Px = P[V][X], Py = P[V][Y], Pz = P[V][Z];

      for (var Rrow = 0; Rrow < 3; Rrow++) // For each row in the R matrix.

      {

        sum = (R[Rrow][X] * Px) + (R[Rrow][Y] * Py) + (R[Rrow][Z] * Pz);

        P[V][Rrow] = sum;

      }

    }     

  }
Surface.prototype.multipt = function(R)

  /*

    Assumes that R is a 3 x 3 matrix and that this.points (i.e., P) is a 3 x n matrix. This method performs P = R * P.

  */

  {

    var Px = 0, Py = 0, Pz = 0; // Variables to hold temporary results.

    var P = this.points; // P is a pointer to the set of surface points (i.e., the set of 3 x 1 vectors).

    var sum; // The sum for each row/column matrix product.



    for (var V = P.length-1; V < P.length; V++) // For all 3 x 1 vectors in the point list.

    {

      Px = P[V][X], Py = P[V][Y], Pz = P[V][Z];

      for (var Rrow = 0; Rrow < 3; Rrow++) // For each row in the R matrix.

      {

        sum = (R[Rrow][X] * Px) + (R[Rrow][Y] * Py) + (R[Rrow][Z] * Pz);

        P[V][Rrow] = sum;

      }

    }     

  }



  // -----------------------------------------------------------------------------------------------------



  Surface.prototype.erase = function()

  {

    var myCanvas = document.getElementById("myCanvas"); // Required for Firefox.

    var ctx = myCanvas.getContext("2d");



    ctx.clearRect(-constants.canvasWidth/2, -constants.canvasHeight/2, myCanvas.width, myCanvas.height);

  }



  // -----------------------------------------------------------------------------------------------------



  Surface.prototype.xRotate = function(sign)

  /*

    Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".

  */

  {

    var Rx = [ [0, 0, 0],

               [0, 0, 0],

               [0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.



    Rx[0][0] = 1;

    Rx[0][1] = 0; // Redundant but helps with clarity.

    Rx[0][2] = 0; 

    Rx[1][0] = 0; 

    Rx[1][1] = Math.cos( sign*constants.dTheta );

    Rx[1][2] = -Math.sin( sign*constants.dTheta );

    Rx[2][0] = 0; 

    Rx[2][1] = Math.sin( sign*constants.dTheta );

    Rx[2][2] = Math.cos( sign*constants.dTheta );



    this.multi(Rx); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P

    this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.

    this.draw();

  }



  // -----------------------------------------------------------------------------------------------------



  Surface.prototype.yRotate = function(sign)

  /*

    Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".

  */      

  {

    var Ry = [ [0, 0, 0],

               [0, 0, 0],

               [0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.



    Ry[0][0] = Math.cos( sign*constants.dTheta );

    Ry[0][1] = 0; // Redundant but helps with clarity.

    Ry[0][2] = Math.sin( sign*constants.dTheta );

    Ry[1][0] = 0; 

    Ry[1][1] = 1;

    Ry[1][2] = 0; 

    Ry[2][0] = -Math.sin( sign*constants.dTheta );

    Ry[2][1] = 0; 

    Ry[2][2] = Math.cos( sign*constants.dTheta );



    this.multi(Ry); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P

    this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.

    this.draw();

  }



  // -----------------------------------------------------------------------------------------------------



  Surface.prototype.zRotate = function(sign)

  /*

    Assumes "sign" is either 1 or -1, which is used to rotate the surface "clockwise" or "counterclockwise".

  */      

  {

    var Rz = [ [0, 0, 0],

               [0, 0, 0],

               [0, 0, 0] ]; // Create an initialized 3 x 3 rotation matrix.



    Rz[0][0] = Math.cos( sign*constants.dTheta );

    Rz[0][1] = -Math.sin( sign*constants.dTheta );        

    Rz[0][2] = 0; // Redundant but helps with clarity.

    Rz[1][0] = Math.sin( sign*constants.dTheta );

    Rz[1][1] = Math.cos( sign*constants.dTheta );

    Rz[1][2] = 0;

    Rz[2][0] = 0

    Rz[2][1] = 0;

    Rz[2][2] = 1;



    this.multi(Rz); // If P is the set of surface points, then this method performs the matrix multiplcation: Rx * P

    this.erase(); // Note that one could use two canvases to speed things up, which also eliminates the need to erase.

    this.draw();

  }


Surface.prototype.xRotatept = function()

  {

    var Rx = [ [0, 0, 0],

               [0, 0, 0],

               [0, 0, 0] ]; 



    Rx[0][0] = 1;

    Rx[0][1] = 0; 

    Rx[0][2] = 0; 

    Rx[1][0] = 0; 

    Rx[1][1] = Math.cos(xangle);

    Rx[1][2] = -Math.sin(xangle);

    Rx[2][0] = 0; 

    Rx[2][1] = Math.sin(xangle);

    Rx[2][2] = Math.cos(xangle);


    this.multipt(Rx); 

    this.erase(); 

    this.draw();

  }




  Surface.prototype.yRotatept = function()


  {

    var Ry = [ [0, 0, 0],

               [0, 0, 0],

               [0, 0, 0] ]; 



    Ry[0][0] = Math.cos(yangle);

    Ry[0][1] = 0;

    Ry[0][2] = Math.sin(yangle);

    Ry[1][0] = 0; 

    Ry[1][1] = 1;

    Ry[1][2] = 0; 

    Ry[2][0] = -Math.sin(yangle);

    Ry[2][1] = 0; 

    Ry[2][2] = Math.cos(yangle);



    this.multipt(Ry); 

    this.erase(); 

    this.draw();

  }




  Surface.prototype.zRotatept = function()



  {

    var Rz = [ [0, 0, 0],

               [0, 0, 0],

               [0, 0, 0] ];



    Rz[0][0] = Math.cos(zangle);

    Rz[0][1] = -Math.sin(zangle);        

    Rz[0][2] = 0; 

    Rz[1][0] = Math.sin(zangle);

    Rz[1][1] = Math.cos(zangle);

    Rz[1][2] = 0;

    Rz[2][0] = 0

    Rz[2][1] = 0;

    Rz[2][2] = 1;



    this.multipt(Rz); 

    this.erase(); 

    this.draw();

  }




  // -----------------------------------------------------------------------------------------------------



  function processKeyDown(evt)

  {                    

    if (evt.ctrlKey)

    {

      switch (evt.keyCode)

      {

        case constants.upArrow: 

          // No operation other than preventing the default behavior of the arrow key.

          evt.preventDefault(); // This prevents the default behavior of the arrow keys, which is to scroll the browser window when scroll bars are present. The user can still scroll the window with the mouse.              

          break;

        case constants.downArrow:

          // No operation other than preventing the default behavior of the arrow key.

          evt.preventDefault();

          break;

        case constants.leftArrow:

          // console.log("ctrl+leftArrow");
                zangle=zangle-0.05;
                update();
        if(zangle<=-2*Math.PI)
        {
            zangle=0;

        }
          surface.zRotate(-1); // The sign determines if the surface rotates "clockwise" or "counterclockwise". 

          evt.preventDefault(); 

          break;

        case constants.rightArrow:

          // console.log("ctrl+rightArrow");
            zangle=zangle+0.05;
            update();
        if(zangle>=2*Math.PI)
        {
            zangle=0;

        }
          surface.zRotate(1);

          evt.preventDefault(); 

          break;

      }

      return; // When the control key is pressed, only the left and right arrows have meaning, no need to process any other key strokes (i.e., bail now).

    }



    // Assert: The control key is not pressed.



    switch (evt.keyCode)

    {

      case constants.upArrow:

        // console.log("upArrow");

        xangle=xangle+0.05;
        update();
        if(xangle>=2*Math.PI)
        {
            xangle=0;

        }

        surface.xRotate(1);

        evt.preventDefault(); 

        break;

      case constants.downArrow:

        // console.log("downArrow");
        xangle=xangle-0.05;
        update();
        if(xangle<=-2*Math.PI)
        {

            xangle=0;
        }

        surface.xRotate(-1); 

        evt.preventDefault(); 

        break;

      case constants.leftArrow:

        // console.log("leftArrow");
        yangle=yangle-0.05;
        update();
        if(yangle<=-2*Math.PI)
        {
            yangle=0;

        }
        surface.yRotate(-1);  

        evt.preventDefault(); 

        break;

      case constants.rightArrow:

        // console.log("rightArrow");
        yangle=yangle+0.05;
        update();
        if(yangle>=2*Math.PI)
        {
            yangle=0;

        }
        surface.yRotate(1);   

        evt.preventDefault(); 

        break;

    }

  }



  // -----------------------------------------------------------------------------------------------------
Surface.prototype.plot = function(x, y, z)
  /*
    add the point (x, y, z)  (in 3 x 1 vector format) to the surface.
  */
  {

        this.points.push(point(x, y, z)); // Store a surface point
        var x=0;
        for (var x = constants.xMin; x <= constants.xMax; x += constants.xDelta)
        {
        this.points.push(point(x, 0, 0));
        }
        p6=1;
        p1=this.points.length-1;
        p4=this.points.length;
        /*var y=-0.2
        for (var x = constants.xMax+1; x <= constants.xMax+2; x += constants.xDelta)
        {
        this.points.push(point(x, y, 0));
        y=y+0.002
        }*/

        /*for (var x = constants.xMax+1; x <= constants.xMax+2; x += constants.xDelta)
        {
        this.points.push(point(11, 0, 0))
        }*/
        for (var x = constants.xMin; x <= constants.xMax; x += constants.yDelta)
        {
        this.points.push(point(0, x, 0));   
        }
        p2=this.points.length-1;
        p5=this.points.length;
        for (var x = constants.xMin; x <= constants.xMax; x += constants.yDelta)
        {
        this.points.push(point(0,0,x)); 
        }
        p3=this.points.length-1;

  }
  Surface.prototype.plot1 = function(x, y, z)
  /*
    add the point (x, y, z)  (in 3 x 1 vector format) to the surface.
  */
  {      


        this.points.push(point(x, y, z)); // Store a surface point
    surface.xRotatept();
    surface.yRotatept();

    surface.zRotatept();
        this.draw();

  }


  function onloadInit()

  {

    appendCanvasElement(); // Create and append the canvas element to the DOM.

    surface.draw(); // Draw the surface on the canvas.

    document.addEventListener('keydown', processKeyDown, false); // Used to detect if the control key has been pressed.

  }



  // -----------------------------------------------------------------------------------------------------




  //surface.generate(); // Creates the set of points reprsenting the surface. Must be called before color().
surface.plot(0,0,0);
  surface.color(); // Based on the min and max z-coordinate values, chooses colors for each point based on the point's z-ccordinate value (i.e., height).

  window.addEventListener('load', onloadInit, false); // Perform processing that must occur after the page has fully loaded.

    </script>

 </head>

  <body>
<table align="center">
<tr><td>
<h5 style="color:#606">Enter the value of (X,Y,Z)</h5>
            <input type="text" value="5" class="num-input" width="50" size="2" id="x-input">
            <input type="text" value="5" class="num-input" width="50" size="2" id="y-input">
            <input type="text" value="2" class="num-input" width="50" size="2" id="z-input">
            <input type="button" value="Plot Point" onClick="surface.plot1(document.getElementById('x-input').value,document.getElementById('y-input').value,document.getElementById('z-input').value); ">

            </td></tr></table>
<table align="center"> <tr><td>
<span id="xa">0</span>deg<br>
<span id="ya">0</span>deg<br>
 <span id="za">0</span>deg</td></tr></table>
 </body>

</html>

推荐答案

沿多轴旋转的最终输出可能会根据您旋转轴的顺序而有所不同.您需要做的是跟踪沿每个轴的总旋转(作为三个数字,不使用矩阵).并且每次更新旋转值时,以正确的顺序将所有三个总旋转应用于单位矩阵(尝试 x、y、z).始终使用相同的顺序.然后用它来转换你的坐标.

The final output of rotations along multiple axis can vary depending on the order that you rotate the axis'. What you need to do is keep track of the total rotation along each axis (as three numbers, not using matrices). And each time you update a rotation value, apply all three total rotations to an identity matrix in the correct order (try x,y,z). Always use the same order. Then use this to transform your coordinates.

这篇关于围绕轴问题旋转画布的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:围绕轴问题旋转画布

基础教程推荐