2017-07-21 133 views
1

我正在開發一種工具,用於從各種模板中修改不同的幾何形狀。這些形狀是可以在房間中找到的基本形狀。 例如:L形,T形,六角形,矩形等轉換幾何形狀,保持對稱性和邊界尺寸不變

我需要做的是使形狀符合所有必要的邊緣,以便在用戶修改邊緣時保持形狀的對稱性和邊界尺寸不變。

形狀是簡單地這樣實現的,與所述第一節點在左上角開始並順時針方向的形狀周圍發生(I使用TypeScript):

public class Shape { 
    private nodes: Array<Node>; 
    private scale: number; // Scale for calculating correct coordinate compared to given length 
    ... // A whole lot of transformation methods 

其然後被繪製爲曲線圖,連接每個節點到數組中的下一個節點。 (見下)

例如,如果我將邊緣C的長度從3.5米改變爲3米,那麼我還希望邊緣E或G改變其長度以保持邊長爲12米,並且還按下E使邊緣D仍然完全水平。 如果我反而將D側改爲2米,那麼B將不得不將其長度改爲10米,依此類推。

(我具有具有傾斜的角度以及形狀等,其角部之一的矩形切斷)

An example of the shapes I have, a T-shape.

問題

用於我有以下代碼修改特定邊緣:

public updateEdgeLength(start: Point, length: number): void { 
     let startNode: Node; 
     let endNode: Node; 
     let nodesSize = this.nodes.length; 

     // Find start node, and then select end node of selected edge. 
     for (let i = 0; i < nodesSize; i++) { 
      if (this.nodes[i].getX() === start.x && this.nodes[i].getY() === start.y) { 
       startNode = this.nodes[i]; 
       endNode = this.nodes[(i + 1) % nodesSize]; 
       break; 
      } 
     } 

     // Calculate linear transformation scalar and create a vector of the edge 
     let scaledLength = (length * this.scale); 
     let edge: Vector = Vector.create([endNode.getX() - startNode.getX(), endNode.getY() - startNode.getY()]); 
     let scalar = scaledLength/startNode.getDistance(endNode); 

     edge = edge.multiply(scalar); 

     // Translate the new vector to its correct position 
     edge = edge.add([startNode.getX(), startNode.getY()]); 
     // Calculate tranlation vector 
     edge = edge.subtract([endNode.getX(), endNode.getY()]); 

     endNode.translate({x: edge.e(1), y: edge.e(2)}); 

    } 

現在我需要一個更一般的cas e用於查找也將需要修改的相應邊緣。我已經開始實現形狀特定的算法,因爲我知道哪些節點對應於形狀的邊緣,但是這對未來不會很有擴展性。

例如,上面的形狀可以有所實現這樣的:

public updateSideLength(edge: Position): void { 
    // Get start node coordinates 
    let startX = edge.start.getX(); 
    let startY = edge.start.getY(); 

    // Find index of start node; 
    let index: num; 
    for (let i = 0; i < this.nodes.length; i++) { 
     let node: Node = this.nodes[i]; 
     if(node.getX() === startX && node.getY() === startY) { 
      index = i; 
      break; 
     } 
    } 

    // Determine side 
    let side: number; 
    if (index === 0 || index === 2) { 
     side = this.TOP; 
    } 
    else if (index === 1 || index === 3 || index === 5) { 
     side = this.RIGHT; 
    } 
    else if (index === 4 || index === 6) { 
     side = this.BOTTOM; 
    }  
    else if (index === 7) { 
     side = this.LEFT; 
    } 

    adaptSideToBoundingBox(index, side); // adapts all other edges of the side except for the one that has been modified 
} 

public adaptSideToBoundingBox(exceptionEdge: number, side: number) { 
    // Modify all other edges 
     // Example: C and G will be modified 
     Move C.end Y-coord to D.start Y-coord; 
     Move G.start Y-coord to D.end Y-coord;  
} 

等等。但是,實施這個用於每個形狀(5大氣壓)和用於將來的形態將是非常耗時的。

所以我想知道的是,如果有一個更一般的方法來解決這個問題?

謝謝!

+0

*任何*幾何形狀?我很肯定,如果你有一個廣義的形狀,定義這種形狀約束將非常困難 – meowgoesthedog

+0

是的,我現在正在修改這個問題,給出更多的例子。 你是對的@meowgoesthedog,我會嘗試更具體的形狀。 –

+0

我刪除了我的評論,+1改善問題,這就是它應該是這樣:) – slhck

回答

1

保留點對的列表以及約束它們並使用它來覆蓋更新上的座標的鍵。

這適用於你給的例子:

var Point = (function() { 
 
    function Point(x, y, connectedTo) { 
 
     if (connectedTo === void 0) { connectedTo = []; } 
 
     this.x = x; 
 
     this.y = y; 
 
     this.connectedTo = connectedTo; 
 
    } 
 
    return Point; 
 
}()); 
 
var Polygon = (function() { 
 
    function Polygon(points, constrains) { 
 
     if (constrains === void 0) { constrains = []; } 
 
     this.points = points; 
 
     this.constrains = constrains; 
 
    } 
 
    return Polygon; 
 
}()); 
 
var Sketch = (function() { 
 
    function Sketch(polygons, canvas) { 
 
     if (polygons === void 0) { polygons = []; } 
 
     if (canvas === void 0) { canvas = document.body.appendChild(document.createElement("canvas")); } 
 
     this.polygons = polygons; 
 
     this.canvas = canvas; 
 
     this.canvas.width = 1000; 
 
     this.canvas.height = 1000; 
 
     this.ctx = this.canvas.getContext("2d"); 
 
     this.ctx.fillStyle = "#0971CE"; 
 
     this.ctx.strokeStyle = "white"; 
 
     this.canvas.onmousedown = this.clickHandler.bind(this); 
 
     this.canvas.onmouseup = this.clickHandler.bind(this); 
 
     this.canvas.onmousemove = this.clickHandler.bind(this); 
 
     requestAnimationFrame(this.draw.bind(this)); 
 
    } 
 
    Sketch.prototype.clickHandler = function (evt) { 
 
     if (evt.type == "mousedown") { 
 
      if (this.selectedPoint != void 0) { 
 
       this.selectedPoint = null; 
 
      } 
 
      else { 
 
       var score = null; 
 
       var best = null; 
 
       for (var p = 0; p < this.polygons.length; p++) { 
 
        var polygon = this.polygons[p]; 
 
        for (var pi = 0; pi < polygon.points.length; pi++) { 
 
         var point = polygon.points[pi]; 
 
         var dist = Math.abs(point.x - evt.offsetX) + Math.abs(point.y - evt.offsetY); 
 
         if (score == null ? true : dist < score) { 
 
          score = dist; 
 
          best = point; 
 
         } 
 
        } 
 
       } 
 
       this.selectedPoint = best; 
 
      } 
 
     } 
 
     if (evt.type == "mousemove" && this.selectedPoint != void 0) { 
 
      this.selectedPoint.x = Math.round(evt.offsetX/5) * 5; 
 
      this.selectedPoint.y = Math.round(evt.offsetY/5) * 5; 
 
      for (var pi = 0; pi < this.polygons.length; pi++) { 
 
       var polygon = this.polygons[pi]; 
 
       if (polygon.points.indexOf(this.selectedPoint) < 0) { 
 
        continue; 
 
       } 
 
       for (var pa = 0; pa < polygon.constrains.length; pa++) { 
 
        var constrain = polygon.constrains[pa]; 
 
        if (constrain.a == this.selectedPoint || constrain.b == this.selectedPoint) { 
 
         constrain.a[constrain.key] = this.selectedPoint[constrain.key]; 
 
         constrain.b[constrain.key] = this.selectedPoint[constrain.key]; 
 
         if (constrain.offset != void 0) { 
 
          if (constrain.a == this.selectedPoint) { 
 
           constrain.b[constrain.key] += constrain.offset; 
 
          } 
 
          else { 
 
           constrain.a[constrain.key] -= constrain.offset; 
 
          } 
 
         } 
 
        } 
 
       } 
 
      } 
 
     } 
 
     requestAnimationFrame(this.draw.bind(this)); 
 
    }; 
 
    Sketch.prototype.draw = function() { 
 
     var ctx = this.ctx; 
 
     //clear 
 
     ctx.fillStyle = "#0971CE"; 
 
     ctx.fillRect(0, 0, 1000, 1000); 
 
     //grid 
 
     ctx.strokeStyle = "rgba(255,255,255,0.25)"; 
 
     for (var x = 0; x <= this.canvas.width; x += 5) { 
 
      ctx.beginPath(); 
 
      ctx.moveTo(x, -1); 
 
      ctx.lineTo(x, this.canvas.height); 
 
      ctx.stroke(); 
 
      ctx.closePath(); 
 
     } 
 
     for (var y = 0; y <= this.canvas.height; y += 5) { 
 
      ctx.beginPath(); 
 
      ctx.moveTo(-1, y); 
 
      ctx.lineTo(this.canvas.width, y); 
 
      ctx.stroke(); 
 
      ctx.closePath(); 
 
     } 
 
     ctx.strokeStyle = "white"; 
 
     ctx.fillStyle = "white"; 
 
     //shapes 
 
     for (var i = 0; i < this.polygons.length; i++) { 
 
      var polygon = this.polygons[i]; 
 
      for (var pa = 0; pa < polygon.points.length; pa++) { 
 
       var pointa = polygon.points[pa]; 
 
       if (pointa == this.selectedPoint) { 
 
        ctx.beginPath(); 
 
        ctx.fillRect(pointa.x - 2, pointa.y - 2, 4, 4); 
 
        ctx.closePath(); 
 
       } 
 
       ctx.beginPath(); 
 
       for (var pb = 0; pb < pointa.connectedTo.length; pb++) { 
 
        var pointb = pointa.connectedTo[pb]; 
 
        if (polygon.points.indexOf(pointb) < pa) { 
 
         continue; 
 
        } 
 
        ctx.moveTo(pointa.x, pointa.y); 
 
        ctx.lineTo(pointb.x, pointb.y); 
 
       } 
 
       ctx.stroke(); 
 
       ctx.closePath(); 
 
      } 
 
     } 
 
    }; 
 
    return Sketch; 
 
}()); 
 
//==Test== 
 
//Build polygon 1 (House) 
 
var poly1 = new Polygon([ 
 
    new Point(10, 10), 
 
    new Point(80, 10), 
 
    new Point(80, 45), 
 
    new Point(130, 45), 
 
    new Point(130, 95), 
 
    new Point(80, 95), 
 
    new Point(80, 135), 
 
    new Point(10, 135), 
 
]); 
 
//Connect dots 
 
for (var x = 0; x < poly1.points.length; x++) { 
 
    var a = poly1.points[x]; 
 
    var b = poly1.points[(x + 1) % poly1.points.length]; 
 
    a.connectedTo.push(b); 
 
    b.connectedTo.push(a); 
 
} 
 
//Setup constrains 
 
for (var x = 0; x < poly1.points.length; x++) { 
 
    var a = poly1.points[x]; 
 
    var b = poly1.points[(x + 1) % poly1.points.length]; 
 
    poly1.constrains.push({ a: a, b: b, key: x % 2 == 1 ? 'x' : 'y' }); 
 
} 
 
poly1.constrains.push({ a: poly1.points[1], b: poly1.points[5], key: 'x' }, { a: poly1.points[2], b: poly1.points[5], key: 'x' }, { a: poly1.points[1], b: poly1.points[6], key: 'x' }, { a: poly1.points[2], b: poly1.points[6], key: 'x' }); 
 
//Build polygon 2 (Triangle) 
 
var poly2 = new Polygon([ 
 
    new Point(250, 250), 
 
    new Point(300, 300), 
 
    new Point(200, 300), 
 
]); 
 
//Connect dots 
 
for (var x = 0; x < poly2.points.length; x++) { 
 
    var a = poly2.points[x]; 
 
    var b = poly2.points[(x + 1) % poly2.points.length]; 
 
    a.connectedTo.push(b); 
 
    b.connectedTo.push(a); 
 
} 
 
//Setup constrains 
 
poly2.constrains.push({ a: poly2.points[0], b: poly2.points[1], key: 'x', offset: 50 }, { a: poly2.points[0], b: poly2.points[1], key: 'y', offset: 50 }); 
 
//Generate sketch 
 
var s = new Sketch([poly1, poly2]);
<!-- TYPESCRIPT --> 
 
<!-- 
 
class Point { 
 
\t constructor(public x: number, public y: number, public connectedTo: Point[] = []) { 
 

 
\t } 
 
} 
 

 
interface IConstrain { 
 
\t a: Point, 
 
\t b: Point, 
 
\t key: string, 
 
\t offset?: number 
 
} 
 

 
class Polygon { 
 
\t constructor(public points: Point[], public constrains: IConstrain[] = []) { 
 

 
\t } 
 
} 
 

 
class Sketch { 
 
\t public ctx: CanvasRenderingContext2D; 
 
\t constructor(public polygons: Polygon[] = [], public canvas = document.body.appendChild(document.createElement("canvas"))) { 
 
\t \t this.canvas.width = 1000; 
 
\t \t this.canvas.height = 1000; 
 

 
\t \t this.ctx = this.canvas.getContext("2d"); 
 
\t \t this.ctx.fillStyle = "#0971CE"; 
 
\t \t this.ctx.strokeStyle = "white"; 
 

 
\t \t this.canvas.onmousedown = this.clickHandler.bind(this) 
 
\t \t this.canvas.onmouseup = this.clickHandler.bind(this) 
 
\t \t this.canvas.onmousemove = this.clickHandler.bind(this) 
 
\t \t requestAnimationFrame(this.draw.bind(this)) 
 
\t } 
 
\t public selectedPoint: Point 
 
\t public clickHandler(evt: MouseEvent) { 
 
\t \t if (evt.type == "mousedown") { 
 
\t \t \t if (this.selectedPoint != void 0) { 
 
\t \t \t \t this.selectedPoint = null; 
 
\t \t \t } else { 
 
\t \t \t \t let score = null; 
 
\t \t \t \t let best = null; 
 
\t \t \t \t for (let p = 0; p < this.polygons.length; p++) { 
 
\t \t \t \t \t let polygon = this.polygons[p]; 
 
\t \t \t \t \t for (let pi = 0; pi < polygon.points.length; pi++) { 
 
\t \t \t \t \t \t let point = polygon.points[pi]; 
 
\t \t \t \t \t \t let dist = Math.abs(point.x - evt.offsetX) + Math.abs(point.y - evt.offsetY) 
 
\t \t \t \t \t \t if (score == null ? true : dist < score) { 
 
\t \t \t \t \t \t \t score = dist; 
 
\t \t \t \t \t \t \t best = point; 
 
\t \t \t \t \t \t } 
 
\t \t \t \t \t } 
 
\t \t \t \t } 
 
\t \t \t \t this.selectedPoint = best; 
 
\t \t \t } 
 
\t \t } 
 
\t \t if (evt.type == "mousemove" && this.selectedPoint != void 0) { 
 
\t \t \t this.selectedPoint.x = Math.round(evt.offsetX/5) * 5; 
 
\t \t \t this.selectedPoint.y = Math.round(evt.offsetY/5) * 5; 
 
\t \t \t for (let pi = 0; pi < this.polygons.length; pi++) { 
 
\t \t \t \t let polygon = this.polygons[pi]; 
 
\t \t \t \t if (polygon.points.indexOf(this.selectedPoint) < 0) { 
 
\t \t \t \t \t continue; 
 
\t \t \t \t } 
 
\t \t \t \t for (let pa = 0; pa < polygon.constrains.length; pa++) { 
 
\t \t \t \t \t let constrain = polygon.constrains[pa]; 
 
\t \t \t \t \t if (constrain.a == this.selectedPoint || constrain.b == this.selectedPoint) { 
 
\t \t \t \t \t \t constrain.a[constrain.key] = this.selectedPoint[constrain.key] 
 
\t \t \t \t \t \t constrain.b[constrain.key] = this.selectedPoint[constrain.key] 
 
\t \t \t \t \t \t if (constrain.offset != void 0) { 
 
\t \t \t \t \t \t \t if (constrain.a == this.selectedPoint) { 
 
\t \t \t \t \t \t \t \t constrain.b[constrain.key] += constrain.offset 
 
\t \t \t \t \t \t \t } else { 
 
\t \t \t \t \t \t \t \t constrain.a[constrain.key] -= constrain.offset 
 
\t \t \t \t \t \t \t } 
 
\t \t \t \t \t \t } 
 
\t \t \t \t \t } 
 
\t \t \t \t } 
 
\t \t \t } 
 
\t \t } 
 
\t \t requestAnimationFrame(this.draw.bind(this)) 
 

 
\t } 
 
\t public draw() { 
 
\t \t var ctx = this.ctx; 
 
\t \t //clear 
 
\t \t ctx.fillStyle = "#0971CE"; 
 
\t \t ctx.fillRect(0, 0, 1000, 1000) 
 
\t \t //grid 
 
\t \t ctx.strokeStyle = "rgba(255,255,255,0.25)" 
 
\t \t for (let x = 0; x <= this.canvas.width; x += 5) { 
 
\t \t \t ctx.beginPath() 
 
\t \t \t ctx.moveTo(x, -1) 
 
\t \t \t ctx.lineTo(x, this.canvas.height) 
 
\t \t \t ctx.stroke(); 
 
\t \t \t ctx.closePath() 
 
\t \t } 
 
\t \t for (let y = 0; y <= this.canvas.height; y += 5) { 
 
\t \t \t ctx.beginPath() 
 
\t \t \t ctx.moveTo(-1, y) 
 
\t \t \t ctx.lineTo(this.canvas.width, y) 
 
\t \t \t ctx.stroke(); 
 
\t \t \t ctx.closePath() 
 
\t \t } 
 
\t \t ctx.strokeStyle = "white" 
 
\t \t ctx.fillStyle = "white"; 
 
\t \t //shapes 
 
\t \t for (let i = 0; i < this.polygons.length; i++) { 
 
\t \t \t let polygon = this.polygons[i]; 
 
\t \t \t for (let pa = 0; pa < polygon.points.length; pa++) { 
 
\t \t \t \t let pointa = polygon.points[pa]; 
 
\t \t \t \t if (pointa == this.selectedPoint) { 
 
\t \t \t \t \t ctx.beginPath(); 
 
\t \t \t \t \t ctx.fillRect(pointa.x - 2, pointa.y - 2, 4, 4) 
 
\t \t \t \t \t ctx.closePath(); 
 
\t \t \t \t } 
 
\t \t \t \t ctx.beginPath(); 
 
\t \t \t \t for (var pb = 0; pb < pointa.connectedTo.length; pb++) { 
 
\t \t \t \t \t var pointb = pointa.connectedTo[pb]; 
 
\t \t \t \t \t if (polygon.points.indexOf(pointb) < pa) { 
 
\t \t \t \t \t \t continue; 
 
\t \t \t \t \t } 
 
\t \t \t \t \t ctx.moveTo(pointa.x, pointa.y) 
 
\t \t \t \t \t ctx.lineTo(pointb.x, pointb.y) 
 
\t \t \t \t } 
 
\t \t \t \t ctx.stroke(); 
 
\t \t \t \t ctx.closePath(); 
 
\t \t \t } 
 
\t \t } 
 
\t } 
 
} 
 

 
//==Test== 
 
//Build polygon 1 (House) 
 
var poly1 = new Polygon([ 
 
\t new Point(10, 10), 
 
\t new Point(80, 10), 
 
\t new Point(80, 45), 
 
\t new Point(130, 45), 
 
\t new Point(130, 95), 
 
\t new Point(80, 95), 
 
\t new Point(80, 135), 
 
\t new Point(10, 135), 
 
]) 
 
//Connect dots 
 
for (let x = 0; x < poly1.points.length; x++) { 
 
\t let a = poly1.points[x]; 
 
\t let b = poly1.points[(x + 1) % poly1.points.length] 
 
\t a.connectedTo.push(b) 
 
\t b.connectedTo.push(a) 
 
} 
 
//Setup constrains 
 
for (let x = 0; x < poly1.points.length; x++) { 
 
\t let a = poly1.points[x]; 
 
\t let b = poly1.points[(x + 1) % poly1.points.length] 
 
\t poly1.constrains.push({ a: a, b: b, key: x % 2 == 1 ? 'x' : 'y' }) 
 
} 
 
poly1.constrains.push(
 
\t { a: poly1.points[1], b: poly1.points[5], key: 'x' }, 
 
\t { a: poly1.points[2], b: poly1.points[5], key: 'x' }, 
 
\t { a: poly1.points[1], b: poly1.points[6], key: 'x' }, 
 
\t { a: poly1.points[2], b: poly1.points[6], key: 'x' } 
 
) 
 
//Build polygon 2 (Triangle) 
 
var poly2 = new Polygon([ 
 
\t new Point(250, 250), 
 
\t new Point(300, 300), 
 
\t new Point(200, 300), 
 
]) 
 
//Connect dots 
 
for (let x = 0; x < poly2.points.length; x++) { 
 
\t let a = poly2.points[x]; 
 
\t let b = poly2.points[(x + 1) % poly2.points.length] 
 
\t a.connectedTo.push(b) 
 
\t b.connectedTo.push(a) 
 
} 
 
//Setup constrains 
 
poly2.constrains.push(
 
\t { a: poly2.points[0], b: poly2.points[1], key: 'x', offset: 50 }, 
 
\t { a: poly2.points[0], b: poly2.points[1], key: 'y', offset: 50 }, 
 
) 
 
//Generate sketch 
 
var s = new Sketch([poly1, poly2]) 
 

 
-->

UPDATE - 基於意見反饋約束偏移

我加了一個 「偏移」 的關鍵限制處理不均衡的關係。

Triangles右上角(至少在最初)受到偏移限制。

+0

哇,謝謝你的偉大答案! 沒有考慮將約束作爲雙向實現。 我會試試這個,但是如果我在一邊傾斜邊緣,它不起作用。 例如: '變種p值=新多邊形([ 新點(10,30), 新點(60,10), 新點(110,10), 新點(140,45), 新點(140,100), 新點(10,100), ]);' 斜邊不需要修改其他任何東西,只是適應邊緣的變化。 –

+0

@FelixNordén你可以添加一個偏移量的約束? –

+0

是的,我也得出了這個結論,謝謝你的幫助! :) –