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(); }
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; }
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) ); }
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]; }
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)]; }
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)]; }
export function TriangleForApex(apex: Coords2d): Triangle2d { return Triangle2d.fromCoords( Coords2d.origin(), Coords2d.fromXY(1, 0), apex ); }
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); }
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; }
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; };
_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; }
_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, }; } }
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; }
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()); }
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); }
static fromXYWH (x: number, y: number, w: number, h: number): AxisRect { return new AxisRect(Coords2d.fromXY(x, y), Size.fromWH(w, h)); }
static fromXYWH (x: number, y: number, width: number, height: number): Rect { return new Rect( Coords2d.fromXY(x, y), OffsetCoords2d.fromDxDy(width, height)); }
// Triangle: the triangle in which we're looking at billiards. get triangle (): Triangle2d { return Triangle2d.fromCoords( Coords2d.origin(), Coords2d.fromXY(1, 0), this._apex); }
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 // of the edge.
_internalMouseMove(x: number, y: number) { if (this._activePoint != null) { this._activePoint.listener(Coords2d.fromXY(x, y)); } }
constructor(renderTarget0: RenderTarget, renderTarget1: RenderTarget) { // Internal UI state this._modelBounds = Rect.fromDimensions(2, 2); this._renderers = [ new RenderableElementsRenderer(renderTarget0), new RenderableElementsRenderer(renderTarget1), ]; this.renderParams = new RenderableElementsRendererParams(); this._needsRedraw = false; // Right panel supports "unfolding" and "dual" this._viewModes = ["", "dual"]; this._apex = Coords2d.fromXY(0.6, 0.6); this._source = TangentCoords2d.fromBaseAndOffset( Coords2d.fromXY(0.5, 0.3), OffsetCoords2d.fromDxDy(1, 0) ); this.vForward = 0; this.vRight = 0; this.vAngle = 0; this.drawDistance = 30; this.skipOdd = false; this.decayParam = 100; this._showPath = true; //this._showUnfolding = true; this._unfoldingViewSize = 7.14; this.edgePaths = [ // Just here for reference, anything less than (1, 1) is acute-only "RLRLRL", // (0, 0, 0) // Winding number 1 with no backtracking: // (<= 1 backtrack means all middle indices are 0) "RRRLRRRLLLRLLL", // Big central triangle (1, 0, 1) "RRRRRLRRRLLLLLRLLL", // Left child (2, 0, 1) "RRRRRLRRRRRLLLLLRLLLLL", // Center child (2, 0, 2) "RRRRRRRLRRRLLLLLLLRLLL", // Left grandchild (3, 0, 1) "RRRRRRRLRRRRRLLLLLLLRLLLLL", // (3, 0, 2) //"RRRRRRRLRRRRRRRLLLLLLLRLLLLLLL", // (3, 0, 3) //"RRRRRRRRRLRRRLLLLLLLLLRLLL", // (4, 0, 1) "RRRRRRRRRLRRRRRLLLLLLLLLRLLLLL", // (4, 0, 2) /*"RRRRRRRRRLRRRRRRRLLLLLLLLLRLLLLLLL", // (4, 0, 3) "RRRRRRRRRLRRRRRRRRRLLLLLLLLLRLLLLLLLLL", // (4, 0, 4) "RRRRRRRRRRRLRRRLLLLLLLLLLLRLLL", // (5, 0, 1) "RRRRRRRRRRRLRRRRRLLLLLLLLLLLRLLLLL", // (5, 0, 2) "RRRRRRRRRRRLRRRRRRRLLLLLLLLLLLRLLLLLLL", // (5, 0, 3) "RRRRRRRRRRRLRRRRRRRRRLLLLLLLLLLLRLLLLLLLLL", // (5, 0, 4) "RRRRRRRRRRRLRRRRRRRRRRRLLLLLLLLLLLRLLLLLLLLLLL", // (5, 0, 5) */ /* "RRRRRRRRRRRRRLRRRLLLLLLLLLLLLLRLLL", // (6, 0, 1) "RRRRRRRRRRRRRLRRRRRLLLLLLLLLLLLLRLLLLL", // (6, 0, 2) "RRRRRRRRRRRRRLRRRRRRRLLLLLLLLLLLLLRLLLLLLL", // (6, 0, 3) "RRRRRRRRRRRRRLRRRRRRRRRLLLLLLLLLLLLLRLLLLLLLLL", // (6, 0, 4) "RRRRRRRRRRRRRLRRRRRRRRRRRLLLLLLLLLLLLLRLLLLLLLLLLL", // (6, 0, 5) "RRRRRRRRRRRRRLRRRRRRRRRRRRRLLLLLLLLLLLLLRLLLLLLLLLLLLL", // (6, 0, 6) // Variant with one backtrack: "RRRRRLRLLRRLLLRLLL", // (2,0,1), [-3, -1, 1, -1, 2, 2] "RRRRRLRRRLLRRLLLRLLLLL", // (2,0,2), [-3, -2, 1, -1, 2, 3] "RRRRRRRLRLLRRLLLLLRLLL", // (3,0,1), [-4, -1, 1, -1, 3, 2] "RRRRRRRLRRRLLRRLLLLLRLLLLL", // (3,0,2) // (3,0,3) yields nothing "RRRRRRRRRLRLLRRLLLLLLLRLLL", // (4,0,1) "RRRRRRRRRLRRRLLRRLLLLLLLRLLLLL", // (4,0,2) // (4,0,3) yields nothing "RRRRRRRRRRRLRLLRRLLLLLLLLLRLLL", // (5,0,1) "RRRRRRRRRRRLRRRLLRRLLLLLLLLLRLLLLL", // (5,0,2) "RRRRRRRRRRRRRLRLLRRLLLLLLLLLLLRLLL", // (6,0,1) "RRRRRRRRRRRRRRRLRLLRRLLLLLLLLLLLLLRLLL", // (7,0,1) // Canonical winding number zero, acute only: //"RLRLRLLRLRLR", // Winding number zero, no mid-cycle backtrack "RRRLRLLLRLLRLLLRLRRR", // (1, 0, 0) [-4, -1, 2, 2, 2, -1] "RRRRRLRLLLLLRLLRLLLLLRLRRRRR", // (2, 0, 0) "RRRRRRRLRLLLLLLLRLLRLLLLLLLRLRRRRRRR", // (3, 0, 0) "RRRRRRRRRLRLLLLLLLLLRLLRLLLLLLLLLRLRRRRRRRRR", // (4, 0, 0) // Winding number 1, two backtracks variant: "RRLLRRRLRRRLLRRLLLRLLL", // (2, 0, 2) "RRRRLLRRRLRRRLLLLRRLLLRLLL", // (3, 0, 2) "RRRRLLRRRLRRRRRLLLLRRLLLRLLLLL", // (3, 0, 3) // Winding number 1, two backtracks: "RRLLRRRRRLRLLRRLLLLLRL", // (3, 1, 1) "RRLLRRRRRLRRRLLRRLLLLLRLLL", // (3, 1, 2) "RRLLRRRRRRRLRLLRRLLLLLLLRL", // (4, 1, 1) "RRRRLLRRRRRLRLLRRLLLLLLLRL", // (4, 1, 1) "RRRRLLRRRRRLRRRLLRRLLLLLLLRLLL", // (4, 1, 2) "RRRRLLRRRRRLRRRLLLLRRLLLLLRLLL", // (4, 1, 2) "RRRRLLRRRRRLRRRLLLLLLRRLLLRLLL", // (4, 1, 2) "RRRRRRLLRRRLRRRLLLLLLRRLLLRLLL", // (4, 1, 2) "RRLLRRRRRRRRRLRLLRRLLLLLLLLLRL", // (5, 1, 1) "RRRRLLRRRRRRRLRLLRRLLLLLLLLLRL", // (5, 1, 1) "RRRRLLRRRRRRRLRLLLLRRLLLLLLLRL", // (5, 1, 1) "RRRRLLRRRRRRRLRRRLLLLRRLLLLLLLRLLL", // (5, 1, 2) "RRRRLLRRRRRRRLRRRLLLLLLRRLLLLLRLLL", // (5, 1, 2) "RRRRRRLLRRRRRLRRRLLLLLLRRLLLLLRLLL", // (5, 1, 2) "RRRRRRLLRRRRRLRRRLLLLLLLLRRLLLRLLL", // (5, 1, 2) "RRLLRRRRRRRRRRRLRLLRRLLLLLLLLLLLRL", // (6, 1, 1) "RRRRLLRRRRRRRRRLRLLRRLLLLLLLLLLLRL", // (6, 1, 1) "RRRRLLRRRRRRRRRLRLLLLRRLLLLLLLLLRL", // (6, 1, 1) "RRRRRRLLRRRRRRRLRLLLLRRLLLLLLLLLRL", // (6, 1, 1) "RRLLRRRRRRRRRRRRRLRLLRRLLLLLLLLLLLLLRL", // (7, 1, 1) "RRRRLLRRRRRRRRRRRLRLLLLRRLLLLLLLLLLLRL", // (7, 1, 1) "RRLLRRRRRRRRRRRRRRRLRLLRRLLLLLLLLLLLLLLLRL", // (8, 1, 1) "RRRRLLRRRRRRRRRRRRRLRLLLLRRLLLLLLLLLLLLLRL", // (8, 1, 1) "RRLLRRRRRRRRRRRRRRRRRLRLLRRLLLLLLLLLLLLLLLLLRL", // (9, 1, 1) "RRRRLLRRRRRRRRRRRRRRRLRLLLLRRLLLLLLLLLLLLLLLRL", // (9, 1, 1) // Winding number 0 "RLRLLRLRRRRLRLRLLRLLLLRLLRLRLRRR", // Begin unclassified "LRLLLLLRRLLRLRRRRRLLLRLRLRRR", "RRRRRRLLRRRRLRLLLLLLLLLRRLLLLRLRRR", "LRLLLLLLLLLRRRLRRRRRRRLLRRRRLLRRRRRRRLRRRLLLLLLLLLRL", "LLRRLRLLLLRLRRLLRRRLRRLRLRLLLRRLLRRLLRLRRR", "LRLLLLLRRRRLLLLRRRRRLRRRRRLLLLRRRRLLLL" // End unclassified */ ]; this.originalEdgePaths = this.edgePaths.map((x) => x); this.highlightCount = 1; }