Example #1
0
export function ApexForBaseAngles(a0: number, a1: number): Coords2d {
  let leftTangent =
    OffsetCoords2d.fromPolar(1, a0).rootedAtCoords(Coords2d.origin());
  let rightTangent = OffsetCoords2d.fromPolar(1, Math.PI - a1)
    .rootedAtCoords(Coords2d.fromXY(1, 0));
  return leftTangent.intersectionWith(rightTangent);
}
Example #2
0
  elementsToRender (frameRect: AxisRect): Array<CanvasRender.Renderable> {
    console.log("BilliardsFlowModel.elementsToRender");

    let image = new ImageRendering(
      AxisRect.fromXYWH(0, 0, 1, 1),
      this.gridSize,
      (imageData: ImageData) => {
        let width = this.gridSize.width;
        let height = this.gridSize.height;
        //let renderScale = this.renderTarget.canvas.renderScale;
        for (let y = 0; y < height; y++) {
          for (let x = 0; x < width; x++) {
            let val = Math.floor(255.9 * this.grid[y * width + x]);


            let offset = y * width + x;
            //let modelCoords = pixelSpaceToModelSpace.transformCoords(
            //  Coords2d.fromXY(x / renderScale, y / renderScale));
            imageData.data[offset*4 + 0] = val;
            imageData.data[offset*4 + 1] = 0;
            imageData.data[offset*4 + 2] = val;
            imageData.data[offset*4 + 3] = 255;
          }
        }
      });

    let line = new Line(Coords2d.fromXY(0, 0), Coords2d.fromXY(1, 0));


    return [image, line];
  }
Example #3
0
  constructor(container: HTMLElement, viewSize: [number, number]) {
    // Internal UI state
    this._viewSize = viewSize;
    this._container = container;
    this._canvasSize = [viewSize[0] / 2, viewSize[1]];
    this._canvasBounds = new Rect(
      Coords2d.fromXY(0, this._canvasSize[1]),
      OffsetCoords2d.fromDxDy(this._canvasSize[0], -this._canvasSize[1])
    );

    this._needsRedraw = false;

    this.gluingBounds = Rect.fromDimensions(2, 2);
    this.editMode = 'triangle';

    // Internal array to store the embedded 3-d triangles.
    this._distance = 3;
    this._triangle = Triangle2d.fromCoords(
      Coords2d.origin(),
      Coords2d.fromXY(1, 0),
      Coords2d.fromXY(0.5, Math.sqrt(3)/4)
    );
    this._rotation = Quaternion.fromReal(1);
    this._gluing = TriangleTetraGluing.fromEdgeIndexAndGluePoint(0, 0);
    this._refreshEmbedding();
  }
Example #4
0
export function TriangleForApex(apex: Coords2d): Triangle2d {
  return Triangle2d.fromCoords(
    Coords2d.origin(),
    Coords2d.fromXY(1, 0),
    apex
  );
}
Example #5
0
  static fromSideLengths (a: number, b: number, c: number): Triangle2d {
    //throw "Triangle.fromSideLengths unimplemented";

    // Numerically stable Heron's formula:
    var area = Math.sqrt(
      (a + (b + c)) * (c - (a - b)) * (c + (a - b)) * (a + (b - c)));

    // Now treat b as the length of the base, with the first vertex of the
    // triangle being the apex, which moves counterclockwise along a.
    var ascent = 2 * area / b;
    // The right-side root from the left side of the base.
    var rightLeft = b - Math.sqrt(c - ascent * ascent);
    // The left-side root from the right side of the base.
    var leftRight = Math.sqrt(a - ascent * ascent);
    var apexX;
    if (rightLeft < 0) {
      apexX = rightLeft;
    } else {
      apexX = leftRight;
    }
    return Triangle2d.fromCoords(
      Coords2d.fromXY(apexX, ascent),
      Coords2d.fromXY(0, 0),
      Coords2d.fromXY(b, 0)
    );
  }
Example #6
0
 vertexCoords (): Array<Coords2d> {
   let {x, y} = this.origin;
   let {width, height} = this.size;
   return [
     Coords2d.fromXY(x, y),
     Coords2d.fromXY(x + width, y),
     Coords2d.fromXY(x + width, y + height),
     Coords2d.fromXY(x, y + height)];
 }
Example #7
0
 vertexCoords (): Array<Coords2d> {
   let o = this.origin;
   let d = this.diagonal;
   return [
     Coords2d.fromXY(o.x, o.y),
     Coords2d.fromXY(o.x + d.dx, o.y),
     Coords2d.fromXY(o.x + d.dx, o.y + d.dy),
     Coords2d.fromXY(o.x, o.y + d.dy)];
 }
Example #8
0
export function addLinesForTriangle(
    elements: Array<Renderable>, origin: Coords2d, sign: number) {
  let v0 = origin;
  let v1 = Coords2d.fromXY(origin.x + sign*triWidth, origin.y);
  let v2 = Coords2d.fromXY(
    origin.x + sign*triWidth/2, origin.y + sign * triHeight);
  elements.push(new Line(v0, v1));
  elements.push(new Line(v1, v2));
  elements.push(new Line(v2, v0));

  elements.push(new Line(
    origin.plus(triBase[0].asOffsetFromOrigin().timesReal(sign)),
    origin.plus(triRight.asOffsetFromOrigin().timesReal(sign))));

  let inner = [
    triInner[0].asOffsetFromOrigin(),
    triInner[1].asOffsetFromOrigin()];
  elements.push(new Line(
    origin.plus(triLeft.asOffsetFromOrigin().timesReal(sign)),
    origin.plus(inner[0].timesReal(sign))));

  elements.push(new Line(
    origin.plus(triBase[2].asOffsetFromOrigin().timesReal(sign)),
    origin.plus(inner[1].timesReal(sign))));

  return elements;
}
Example #9
0
  elementsToRender (frameRect: AxisRect): Array<CanvasRender.Renderable> {
    let elements: Array<CanvasRender.Renderable> = [];
    let boundaryRect = AxisRect.fromXYWH(0, 0, this.columnCount, this.rowCount);

    let borderElement = Polygon.fromRect(boundaryRect.addMargin(0.35));
    elements.push(borderElement);

    let colors = ['#eeeeee', '#fae', '#000066'];

    for (let i = 0; i < this.rowCount; i++) {
      let y = (i + 0.5);
      for (let j = 0; j < this.columnCount; j++) {
        let x = (j + 0.5);
        let value = this.grid(this.rowCount - i - 1, j);
        let color = '#333333';
        if (value >= 0 && value < colors.length) {
          color = colors[value];
        }
        let cellRect = AxisRect.fromCenterAndDimensions(
          Coords2d.fromXY(x, y), 1, 1);
        let cellElement = Polygon.fromRect(cellRect.scaleBy(0.9));
        cellElement.style.fillStyle = color;
        cellElement.style.strokeStyle = "";
        elements.push(cellElement);
      }
    }


    return elements;
  }
Example #10
0
      return (newCanvasCoords: Coords2d) => {
        var newModelCoords =
          canvasToModel.transformCoords(newCanvasCoords);
        if (newModelCoords.y < 0.01) {
          newModelCoords.y = 0.01;
        }
        if (newModelCoords.x < 0) {
          newModelCoords.x = 0;
        }
        if (newModelCoords.x > 1) {
          newModelCoords.x = 1;
        }
        var modelDelta = newModelCoords.asOffsetFrom(modelCoords);
        let newApexCoords = originalApexCoords.plus(modelDelta);

        let newSourceY = newApexCoords.y * originalSourceVert;
        let newSourceX = newApexCoords.x +
          (originalSourceHorz - newApexCoords.x) *
          (1.0 - originalSourceVert);

        this.apex = newApexCoords;
        this.source.base = Coords2d.fromXY(newSourceX, newSourceY);
        this.source.offset.dy = originalSource.offset.dy *
          (newApexCoords.y / originalApexCoords.y);
        this.source.offset = this.source.offset.normalized();
        this._needsRedraw = true;
      };
Example #11
0
        return (newCanvasCoords: Coords2d, final: boolean) => {
          let canvasOffset = newCanvasCoords.asOffsetFrom(canvasCoords);
          let modelOffset = canvasToModel.transformOffset(canvasOffset);
          let indexOffset = Math.round(modelOffset.dx / intervalWidth);
          let newIndex = selectedIndex + indexOffset;

          if (currentIndex != newIndex) {
            currentIndex = newIndex;
            let forwardMap: Array<number> = originalPermutation.forwardMap;
            let newForwardMap: Array<number> = forwardMap.map(
              (index: number) => {
                if (index < Math.min(selectedIndex, newIndex) ||
                  index > Math.max(selectedIndex, newIndex)) {
                  return index;
                }
                if (index == selectedIndex) {
                  return newIndex;
                }
                if (selectedIndex < newIndex) {
                  return index - 1;
                }
                return index + 1;
              });
            let newPermutation =
              Permutation.fromForwardMap(newForwardMap);
            if (selectedRow == 0) {
              this.inputOrder = newPermutation;
            } else {
              this.outputOrder = newPermutation;
            }
            this.setNeedsRedraw();
          }
        };
Example #12
0
 vectorForAngleIndex (i: number): OffsetCoords2d {
   let v: OffsetCoords2d;
   if (i == 0) {
     v = this.apex.asOffsetFromOrigin();
   } else {
     v = OffsetCoords2d.fromDxDy(1 - this.apex.x, -this.apex.y);
   }
   return v.times(v).timesReal(1.0 / v.squaredLength());
 }
Example #13
0
 _thingie () {
   for (let i = 0; i < this.edgePaths.length; i++) {
     if (!EdgePathIsClosed(this.edgePaths[i])) {
       window.console.log(
         `Warning: edge path ${i} (${this.edgePaths[i]}) is not closed`);
     }
   }
   let edgeSequences = this.edgePaths.map(EdgeSequenceForEdgePath);
   let cutoff = edgeSequences.length - this.highlightCount;
   this.extra = [];
   let h = Math.sqrt(3) / 3;
   let w = 1;
   let tri = this.triangle;
   //let xres = 100;
   //let yres = 50;
   //let dotRadius = 0.007;
   let xres = 200;
   let yres = 100;
   let dotRadius = 0.0045;
   let highlighted = 0;
   let sequenceCounts = edgeSequences.map(() => 0);
   for (let y = 1; y <= yres; y++) {
     for (let x = 0; x <= xres; x++) {
       for (let i = 0; i < edgeSequences.length; i++) {
       //for (let i = edgeSequences.length - 1; i >= 0; i--) {
         let edgeSequence = edgeSequences[i];
         let v = tri.vertexCoords;
         v[2] = Coords2d.fromXY(v[0].x + x * w / xres, v[0].y + y * h / yres);
         let margin = PathMargin(tri, edgeSequence);
         if (margin > 0) {
           let c = CanvasRender.Circle.fromCenterAndRadius(v[2], dotRadius);
           if (i >= cutoff) {
             c.style.fillStyle = '#bb8888';
             highlighted++;
           } else {
             let intensity = Math.min(Math.sqrt(margin), 1);
             let rg = Math.floor(128 * (1 - intensity));
             let b = Math.floor(rg + 255 * intensity);
             c.style.fillStyle = `rgb(${rg},${rg},${b}`;
           }
           this.extra.push(c);
           sequenceCounts[i]++;
           break;
         }
       }
     }
   }
   window.console.log(`${highlighted} points were highlighted`);
   this.sequenceResults = [];
   for (let i = 0; i < sequenceCounts.length; i++) {
     if (sequenceCounts[i] > 0) {
       this.sequenceResults.push([i, sequenceCounts[i]]);
     }
   }
   this._needsRedraw = true;
 }
Example #14
0
  _internalMouseDown(x: number, y: number) {
    var canvasCoords = Coords2d.fromXY(x, y);
    let listener = this.controlPointsCallback(canvasCoords);

    if (listener != null) {
      this._isMouseDown = true;
      this._activePoint = {
        listener: listener,
        canvasCoords: canvasCoords,
      };
    }
  }
Example #15
0
 nearestVertexTo (coords: Coords2d): number {
   var bestVertex = -1;
   var bestDist = 0;
   for (var i = 0; i < 3; i++) {
     var distance = coords.distanceFrom(this.vertexCoords[i]);
     if (bestVertex < 0 || distance < bestDist) {
       bestVertex = i;
       bestDist = distance;
     }
   }
   return bestVertex;
 }
Example #16
0
            return (newCanvasCoords: Coords2d, final: boolean) => {
              let canvasOffset = newCanvasCoords.asOffsetFrom(canvasCoords);
              let modelOffset = canvasToModel.transformOffset(canvasOffset);
              let newPosition = modelCoords.x + modelOffset.dx;
              let delta = newPosition - modelCoords.x;
              let rationalDelta = Rational.approximationForNumber(delta, 5);
              let newLeftBoundary =
                intervals[oldIndex].leftBoundary.plus(rationalDelta);

              // Find the output interval index closest to newLeftBoundary
              let curPos = intervals[0].leftBoundary;
              let newIndex = 0;
              for (let i = 0; i < intervals.length; i++) {
                if (i == oldIndex) {
                  continue;
                }
                let curLength = intervals[i].length;
                let centerPos =
                  curPos.plus(curLength.dividedBy(Rational.fromInt(2)));
                if (centerPos.greaterThan(newLeftBoundary)) {
                  break;
                }
                newIndex++;
                curPos = curPos.plus(curLength);
              }

              if (currentIndex != newIndex) {
                console.log(`Moving to index ${newIndex}`);
                currentIndex = newIndex;
                let forwardMap: Array<number> =
                  intervalExchangeMap.permutation.forwardMap;
                let newForwardMap: Array<number> = forwardMap.map(
                  (outputIndex: number) => {
                    if (outputIndex < Math.min(oldIndex, newIndex) ||
                      outputIndex > Math.max(oldIndex, newIndex)) {
                      return outputIndex;
                    }
                    if (outputIndex == oldIndex) {
                      return newIndex;
                    }
                    if (oldIndex < newIndex) {
                      return outputIndex - 1;
                    }
                    return outputIndex + 1;
                  });
                let newPermutation =
                  Permutation.fromForwardMap(newForwardMap);
                this.intervalExchangeMap.set(
                  IntervalExchangeMap.fromLengthsAndPermutation(
                    intervalExchangeMap.intervalLengths, newPermutation));
              }
            };
Example #17
0
 return (newCanvasCoords: Coords2d, final: boolean) => {
   let canvasOffset = newCanvasCoords.asOffsetFrom(canvasCoords);
   let modelOffset = canvasToModel.transformOffset(canvasOffset);
   let newPosition = modelCoords.x + modelOffset.dx;
   newPosition = Math.max(newPosition, minValue);
   newPosition = Math.min(newPosition, maxValue);
   let delta = newPosition - modelCoords.x;
   let rationalDelta = Rational.approximationForNumber(delta, 5);
   let newLengths = intervalExchangeMap.intervalLengths.map((x) => x);
   newLengths[targetIndex] =
     newLengths[targetIndex].plus(rationalDelta);
   newLengths[targetIndex + 1] =
     newLengths[targetIndex + 1].minus(rationalDelta);
   this.intervalExchangeMap.set(
     IntervalExchangeMap.fromLengthsAndPermutation(
       newLengths, intervalExchangeMap.permutation));
 };
Example #18
0
  elementsToRender (modelFrame: AxisRect): Array<Renderable> {
    let horizontalShift = true;
    let trueCenter = false;
    let strokeStyle = '#ff33ee';
    let lineWidth = '2';

    let elements: Array<Renderable> = [];
    console.log("elementsToRender");
    let xOffset = 0;
    if (trueCenter) {
      xOffset += centerOffset;
    }
    if (horizontalShift) {
      xOffset += triWidth / 2;
    }
    for (let i = -10; i <= 10; i++) {
      let origin = Coords2d.fromXY(i * triWidth, 1.5);
      addLinesForTriangle(elements,
        Coords2d.fromXY(origin.x, origin.y), 1);
      addLinesForTriangle(elements,
        Coords2d.fromXY(origin.x + triWidth/2, origin.y + triHeight), -1);

      addLinesForTriangle(elements,
        Coords2d.fromXY(origin.x + xOffset, origin.y - triHeight), 1);
      addLinesForTriangle(elements,
        Coords2d.fromXY(origin.x + triWidth/2 + xOffset, origin.y), -1);
    }

    let rowShift = 0.5721554288609622;
    // The angle of the fundamental interval we want to map to the
    // x axis
    let angle = Math.atan2(-1, rowShift);
    // The distance of each cell along the x axis
    let dx = Math.sqrt(1 + rowShift * rowShift);
    for (let i = -10; i <= 10; i++) {
      let origin = Coords2d.fromXY(i * dx, -3);
      addLinesForSquare(elements, origin, -angle);
      /*for (let j = 0; j < 3; j++) {
        addLinesForSquare(elements,
          Coords2d.fromXY(origin.x + rowShift * j, origin.y - j),
          0);//-angle);
      }*/
    }

    for (let i = 0; i < elements.length; i++) {
      let e = elements[i];
      e.style.strokeStyle = strokeStyle;
      e.style.lineWidth = lineWidth;
    }
    return elements;
  }
Example #19
0
export function PathMargin (
    triangle: Triangle2d, edgeSequence: Array<number>): number {
  let tri = triangle.copy();
  let leftBoundary = [];
  let rightBoundary = [];
  let prevEdge = 0;
  for (let i = 0; i < edgeSequence.length; i++) {
    let edgeDelta = (edgeSequence[i] - (prevEdge + 1) + 3) % 3; // always 0 or 1
    if (edgeDelta == i % 2) {
      // Right
      leftBoundary.push(tri.vertex(edgeSequence[i] + 1 - (i % 2)));
    } else {
      // Left
      rightBoundary.push(tri.vertex(edgeSequence[i] + (i % 2)));
    }
    tri.reflectThroughEdge(edgeSequence[i]);
    prevEdge = edgeSequence[i];
  }
  let offset = tri.vertexCoords[0]
    .asOffsetFrom(triangle.vertexCoords[0]);
  let lineCoeff = OffsetCoords2d.fromDxDy(-offset.dy, offset.dx).normalized();
  let o = Coords2d.origin();
  let minLeft = lineCoeff.dot(triangle.vertexCoords[0].asOffsetFrom(o));
  for (let i = 0; i < leftBoundary.length; i++) {
    let coeff = lineCoeff.dot(leftBoundary[i].asOffsetFrom(o));
    if (coeff < minLeft) {
      minLeft = coeff;
    }
  }
  let maxRight = lineCoeff.dot(triangle.vertexCoords[1].asOffsetFrom(o));
  for (let i = 0; i < rightBoundary.length; i++) {
    let coeff = lineCoeff.dot(rightBoundary[i].asOffsetFrom(o));
    if (coeff > maxRight) {
      maxRight = coeff;
    }
  }
  //window.console.log(`${minLeft}, ${maxRight}`);
  return minLeft - maxRight;
}
Example #20
0
  constructor(renderTarget: RenderTarget) {
    this._renderTarget = renderTarget;
    this.renderParams = new BilliardsPhaseRendererParams();
    this._renderer =
      new BilliardsPhaseRenderer(renderTarget.canvas, _paletteShader);

    this._redrawCounter = 1000;
    this._needsRedraw = false;
    this._controlPointsInterface = null;

    // Exposed properties
    this.numerator = ComplexPoly.fromReal(1);
    this.denominator = ComplexPoly.fromReal(1);
    this.phaseOffset = 0;
    this.modelBounds = AxisRect.fromXYWH(-1, -1, 2, 2);
    this._renderer.renderFrom(this.modelBounds);

    // The coordinates constantOffset was derived from.
    this._offsetCoords = Coords2d.fromXY(0, 0);

    this._createControlInterfaces();

    requestAnimationFrame(this.generateClockTick());
  }
Example #21
0
 constructor (target: RenderTarget) {
   this.target = target;
   this.modelBounds = AxisRect.fromCenterAndDimensions(
     Coords2d.origin(), 2, 2);
   this.backgroundColor = "white";
 }
Example #22
0
 center (): Coords2d {
   return this.origin.plus(this.size.asOffsetCoords().timesReal(0.5));
 }
Example #23
0
 static fromCenterAndDimensions (
     center: Coords2d, width: number, height: number): AxisRect {
   let origin = Coords2d.fromXY(center.x - width/2, center.y - height/2);
   let size = Size.fromWH(width, height);
   return new AxisRect(origin, size);
 }
Example #24
0
 static fromXYWH (x: number, y: number, w: number, h: number): AxisRect {
   return new AxisRect(Coords2d.fromXY(x, y), Size.fromWH(w, h));
 }
Example #25
0
 constructor (start: Coords2d, end: Coords2d) {
   super();
   this.start = start.copy();
   this.end = end.copy();
   this.lineDash = null;
 }
Example #26
0
// @flow

import { ElementsModel } from 'faec/elements_model';
import { Coords2d, OffsetCoords2d } from 'faec/geometry';
import { AxisRect } from 'faec/polygon';

import { Line, Renderable } from 'faec/element_render';

let squareRoot = Math.sqrt(3);
let fourthRoot = Math.sqrt(squareRoot);
// (w, h) are the dimensions of the unit-area equilateral triangle
// with its base aligned with the x axis.
let triWidth = 2 / fourthRoot;
let triHeight = fourthRoot;
let triVerts = [
  Coords2d.origin(),
  Coords2d.fromXY(triWidth, 0),
  Coords2d.fromXY(triWidth / 2, triHeight)];
let triLeft = Coords2d.midpoint(triVerts[0], triVerts[2]);
let triRight = Coords2d.midpoint(triVerts[1], triVerts[2]);

// The horizontal width of the long diagonal, which goes from
// triRight to the base and has length 1.
let diagonalWidth = Math.sqrt(1.0 - triRight.y * triRight.y);
let diagonal = OffsetCoords2d.fromDxDy(diagonalWidth, triRight.y);
let triBase = [
  Coords2d.fromXY(triRight.x - diagonalWidth, 0),
  Coords2d.fromXY((triRight.x - diagonalWidth) * 2, 0),
  Coords2d.fromXY((triRight.x - diagonalWidth) + triWidth/2, 0)];

// The offset of the center base point from the geometric center
Example #27
0
 center (): Coords2d {
   return this.origin.plus(this.diagonal.timesReal(0.5));
 }
Example #28
0
 static fromDimensions (width: number, height: number): Rect {
   return Rect.fromCenterAndDimensions(Coords2d.origin(), width, height);
 }
Example #29
0
 static fromXYWH (x: number, y: number, width: number, height: number): Rect {
   return new Rect(
     Coords2d.fromXY(x, y), OffsetCoords2d.fromDxDy(width, height));
 }
Example #30
0
export function addLinesForSquare(
    elements: Array<Renderable>, origin: Coords2d, angle: number) {
  // Extract the dimensions for the capstone triangle from its
  // simplest orientation in the base of the equilateral triangle.

  let bottom = capLeft;
  let top = 1 - capLeft;

  let sin = Math.sin(angle);
  let cos = Math.cos(angle);
  let basis = [
    OffsetCoords2d.fromDxDy(cos, sin),
    OffsetCoords2d.fromDxDy(-sin, cos),
  ];

  // All six corners in two adjacent grid squares, ordered
  // widdershins from the base.
  let corners = [
    origin,
    origin.plus(basis[0]),
    origin.plus(basis[0].timesReal(2)),
    origin.plus(basis[0].timesReal(2)).plus(basis[1]),
    origin.plus(basis[0]).plus(basis[1]),
    origin.plus(basis[1])];
  elements.push(new Line(corners[0], corners[2]));
  elements.push(new Line(corners[2], corners[3]));
  elements.push(new Line(corners[3], corners[5]));
  elements.push(new Line(corners[5], corners[0]));
  elements.push(new Line(corners[1], corners[4]));

  let vTop = [
    corners[5].plus(basis[0].timesReal(top)),
    corners[3].plus(basis[0].timesReal(-bottom))];
  let vBottom = [
    corners[0].plus(basis[0].timesReal(bottom)),
    corners[2].plus(basis[0].timesReal(-top))];
  let vLeft = corners[0].plus(basis[1].timesReal(0.5));
  let vCenter = corners[1].plus(basis[1].timesReal(0.5));
  let vRight = corners[2].plus(basis[1].timesReal(0.5));

  let innerVecs = [
    vLeft.asOffsetFrom(vBottom[0]),
    vRight.asOffsetFrom(vTop[1])];
  let innerCoeff = triBase[0].x / (triWidth / 2);
  let vInner = [
    vBottom[0].plus(innerVecs[0].timesReal(innerCoeff)),
    vTop[1].plus(innerVecs[1].timesReal(innerCoeff))];

  elements.push(new Line(vLeft, vBottom[0]));
  elements.push(new Line(vInner[0], vCenter));
  elements.push(new Line(vInner[0], vTop[0]));

  elements.push(new Line(vTop[1], vRight));
  elements.push(new Line(vInner[1], vCenter));
  elements.push(new Line(vInner[1], vBottom[1]));

  let vOutsideLeft = corners[5].plus(basis[0].timesReal(-capLeft));
  elements.push(new Line(vOutsideLeft, vLeft));

  let vOutsideRight = corners[2].plus(basis[0].timesReal(capLeft));
  elements.push(new Line(vRight, vOutsideRight));

  console.log(`Horizontal shift: ${1 - top}`);
}