根据所选选项更改画布尺寸

13

我正在使用画布(canvas)工作,并考虑改变立方体的尺寸。因此,我使用HTML5 Canvas制作了这个立方体,它由两个正方形通过线连接在一起以使其看起来像一个立方体。

我的要求是从选择中选择一个立方体类型后,立方体应根据所选选项的长度和宽度自动更改自身。高度保持不变。例如,如果我选择默认为立方体的5x5的立方体,然后选择5x10选项,则宽度(前部)不应更改,但立方体的长度(侧部)应扩展,反之亦然,如果我选择10x5,则最大选项是25x15。如您所见,我在下面创建的画布以像素为单位,首先需要将这些像素转换为厘米(cm),然后再将厘米转换为立方米。

整个立方体应对齐在指定的固定画布区域内。

这里是fiddle

var canvas = document.querySelector('canvas');

canvas.width = 500;
canvas.height = 300;

var contxt = canvas.getContext('2d');

//squares
/*
contxt.fillRect(x, y, widht, height);
*/
contxt.strokeStyle = 'grey';
var fillRect = false;
contxt.fillStyle = 'rgba(0, 0, 0, 0.2)';
contxt.rect(80, 80, 100, 100);
contxt.rect(120, 40, 100, 100);
if (fillRect) {
  contxt.fill();
}
contxt.stroke();

/*Lines
contxt.beginPath();
contxt.moveTo(x, y);
contxt.lineTo(300, 100);
*/
contxt.beginPath();

contxt.moveTo(80, 80);
contxt.lineTo(120, 40);

contxt.moveTo(180, 80);
contxt.lineTo(220, 40);

contxt.moveTo(80, 180);
contxt.lineTo(120, 140);

contxt.moveTo(180, 180);
contxt.lineTo(220, 140);

contxt.stroke();
canvas {
  border: 1px solid #000;
}
select {
  display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select>
  <option>5x5</option>
  <option>5x10</option>
  <option>10x5</option>
</select>

<canvas></canvas>


1
请注意,5x5是正方形,不是立方体。5x10是矩形。对于立方体,应该像5x5x5这样。但无论如何,假设5x10和10x5是立方体...那么深度必须是多少呢?例如,5x10表示5x10x5还是5x10x10? - Takit Isy
@Takit Isy 抱歉我没理解到逻辑,感谢您的纠正。如果您选择5x10,则5是宽度,10是深度,反之亦然,如果您选择10x5。 - Mohammed Wahed Khan
高度是共同的。因此,如果我选择5x10,则高度为5,宽度为5,深度为10。 - Mohammed Wahed Khan
2个回答

16

绘制立方体:

为了生成一个动态立方体,您需要监听


我可以对它应用一些过渡效果吗?那太好了。 - Mohammed Wahed Khan
2
当然。我已经编辑了答案以支持过渡效果。希望这就是你要找的。 - Bernhard

7

绘制一个拉伸的轮廓线。 等轴测投影

理想情况下,您应该创建一个通用的等轴测渲染器,根据平面图将对象按需呈现到画布上。

然后,您可以将计划链接到选择框,并在选择更改时更新视图。

最佳代码示例

以下示例使用对象renderIsoPlan来呈现形状。

形状通过平面图设置。例如,一个盒子有一个地面平面图[[-1,-1],[1,-1],[1,1],[-1,1]],表示底部的四个角落。

renderIsoPlan具有以下属性

  • canvas 将形状呈现到的画布。在设置之前不会绘制。 renderIsoPlan将创建一个2D上下文,如果您已经有一个,则将是相同的
  • height 投影轮廓向上的距离。
  • style 画布上下文样式对象,例如{stokeStyle : "red", lineWidth : 2}绘制红色线条的2个像素。
  • plan 地板的一组点。点会自动移动到中心。例如[[0,-1],[1,1],[-1,1]]绘制三角形
  • scale 缩放,不再多说
  • rotate 旋转量。如果不为0,则使用等轴测投影,否则使用斜轴测投影。
  • centerY 以画布单位大小为基准。即0.5为中心
  • centerX 同上

调用renderIsoPlan.refresh进行绘制

请注意,在问题中无法旋转投影,因为它在视觉上似乎会扭曲(改变形状),因此如果旋转不为0,则使用不同的投影。

请注意,对象会自动居中于0,0,请使用centerXcenterY将其居中于视图中

setTimeout(start,0); // wait till Javascript parsed and executed
requestAnimationFrame(animate); // Animate checked at start so start anim

// named list of shapes
const boxes = {
  box1By1 : {
    plan : [[-1,-1],[1,-1],[1,1],[-1,1]],
    scale : 35,
    centerY : 0.75,
  },
  box1By2 : {
    plan :  [[-1,-2],[1,-2],[1,2],[-1,2]],
    scale : 30,
    centerY : 0.7,
  },
  box2By2 : {
    plan :  [[-2,-2],[2,-2],[2,2],[-2,2]],
    scale : 25,
    centerY : 0.7,
  },
  box2By1 : {
    plan :  [[-2,-1],[2,-1],[2,1],[-2,1]],
    scale : 30,
    centerY : 0.7,
  },
  box1By3 : {
    plan : [[-1,-3],[1,-3],[1,3],[-1,3]],
    scale : 22,
    centerY : 0.67,
  },
  box1By4 :{
    plan :  [[-1,-4],[1,-4],[1,4],[-1,4]],
    scale : 20,
    centerY : 0.63,
  },
  lShape : {
    plan : [[-2,-4],[0,-4],[0,2],[2,2],[2,4],[-2,4]],
    scale : 20,
    centerY : 0.65,
 },
  current : null,
}
// Sets the renderIsoPlan object to the current selection
function setShape(){
  boxes.current = boxes[boxShape.value];
  Object.assign(renderIsoPlan, boxes.current);
  if (!animateCheckBox.checked) { renderIsoPlan.refresh() }
}
// When ready this is called
function start(){
  renderIsoPlan.canvas = canvas;
  renderIsoPlan.height = 2;
  setShape();
  renderIsoPlan.refresh();
}

// Add event listeners for checkbox and box selection 
boxShape.addEventListener("change", setShape );
animateCheckBox.addEventListener("change",()=>{
  if (animateCheckBox.checked) {
    requestAnimationFrame(animate);
  } else {
    renderIsoPlan.rotate = 0;
    setShape();
  }
});


// Renders animated object
function animate(time){     
  if (animateCheckBox.checked) {
    renderIsoPlan.rotate = time / 1000;
    renderIsoPlan.refresh();
    requestAnimationFrame(animate);
  }
}


// Encasulate Axonometric render.
const renderIsoPlan = (() => {
    var ctx,canvas,plan,cx,cy,w,h,scale,height, rotate;
    height = 50;
    scale = 10;
    rotate = 0;
    const style = {
      strokeStyle : "#000",
      lineWidth : 1,
      lineJoin : "round",
      lineCap : "round",
    };
    const depthScale = (2/3);

    // Transforms then projects the point to 2D
    function transProjPoint(p) {
      const project = rotate !== 0 ? 0 : depthScale;
      const xdx = Math.cos(rotate);
      const xdy = Math.sin(rotate);
      const y = p[0] * xdy + p[1] * xdx;
      const x = p[0] * xdx - p[1] * xdy - y * project;
      return [x,y * depthScale];
    }
    
    // draws the plan        
    function draw() {
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.clearRect(0,0,w,h);      
      ctx.setTransform(scale, 0, 0, scale, cx, cy);
      var i = plan.length;
      ctx.beginPath();
      while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
      ctx.closePath();
      i = plan.length;
      ctx.translate(0,-height);
      ctx.moveTo(...transProjPoint(plan[--i]))
      while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
      ctx.closePath();
      i = plan.length;
      while(i--){
        const [x,y] = transProjPoint(plan[i]);
        ctx.moveTo(x,y);
        ctx.lineTo(x,y + height);
      }
      ctx.setTransform(1, 0, 0, 1, 0, 0);
      ctx.stroke();

    }
    // centers the plan view on coordinate 0,0
    function centerPlan(plan){
      var x = 0, y = 0;
      for(const point of plan){
         x += point[0];
         y += point[1];
      }
      x /= plan.length;
      y /= plan.length;
      for(const point of plan){
         point[0] -= x;
         point[1] -= y;
      }
      return plan;
    }    
    
    
    // Sets the style of the rendering
    function setStyle(){
      for(const key of Object.keys(style)){
        if(ctx[key] !== undefined){
          ctx[key] = style[key];
        }
      }
    }


  // define the interface
  const API = {
    // setters allow the use of Object.apply 
    set canvas(c) {
      canvas = c;
      ctx = canvas.getContext("2d");
      w = canvas.width;  // set width and height
      h = canvas.height;
      cx = w / 2 | 0;    // get center
      cy = h / 2 | 0; // move center down because plan is extruded up
    },
    set height(hh) { height = hh },
    set style(s) { Object.assign(style,s) },
    set plan(points) { plan = centerPlan([...points])  },
    set scale(s) { scale = s },
    set rotate(r) { rotate = r },
    set centerY(c) { cy = c * h },
    set centerX(c) { cx = c * w },
    
    // getters not used in the demo
    get height() { return height },
    get style() { return style },
    get plan() { return plan },
    get scale() { return scale },
    get rotate() { return r },
    get centerY() { return cy / h },
    get centerX() { return cx / w },
    
    // Call this to refresh the view
    refresh(){
      if(ctx && plan){
        ctx.save();
        if(style){ setStyle() }
        draw();
        ctx.restore();
      }
    }
  }
  // return the interface
  return API;
})();
canvas { border : 2px solid black; }
<select id="boxShape">
 <option value = "box1By1">1 by 1</option>
 <option value = "box1By2">1 by 2</option>
 <option value = "box2By2">2 by 2</option>
 <option value = "box2By1">2 by 1</option>
 <option value = "box1By3">1 by 3</option>
 <option value = "box1By4">1 by 4</option>
 <option value = "lShape">L shape</option>
</select>
<input type="checkBox" id="animateCheckBox" checked=true>Animate</input><br>
<canvas id="canvas"></canvas>


renderIsoPlan 是从哪里来的? - edixon
@edixon 它是代码片段底部定义的对象,从const renderIsoPlan = (() => {这一行开始。 - Blindman67
谢谢。我试图将所有内容放在一个HTML文件中,但renderIsoPlan未定义。将script移动到末尾解决了问题。 - edixon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接