plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
207 lines (168 loc) • 7.88 kB
text/typescript
/**
* @classdesc The irregular hexagon tile from the Girih set.
*
* @requires Bounds
* @requires Circle
* @requires GirihTile
* @requires Line
* @requires Polygon
* @requires TileType
* @requires Vertex
*
* @author Ikaros Kappler
* @date 2013-11-28
* @modified 2014-04-05 Ikaros Kappler (member array outerTilePolygons added).
* @modified 2015-03-19 Ikaros Kappler (added toSVG()).
* @modified 2020-10-31 Refactored to work with PlotBoilerplate.
* @modified 2020-11-13 Ported from vanilla JS to TypeScript.
* @version 2.0.1-alpha
* @file GirihHexagon
* @public
**/
import { Circle } from "../../Circle";
import { GirihTile, TileType } from "./GirihTile";
import { Line } from "../../Line";
import { Polygon } from "../../Polygon";
import { Vertex } from "../../Vertex";
export class GirihHexagon extends GirihTile {
/**
* @constructor
* @extends GirihTile
* @name GirihHexagon
* @param {Vertex} position
* @param {number} edgeLength
*/
constructor( position:Vertex, edgeLength?:number ) {
super( position, edgeLength, TileType.IRREGULAR_HEXAGON );
// Overwrite the default symmetries:
// the hexagon tile has a 180° symmetry (5/10 * 360°)
this.uniqueSymmetries = 5;
// Init the actual decahedron shape with the passed size
let pointA:Vertex = new Vertex(0,0);
let pointB:Vertex = pointA;
const startPoint:Vertex = pointA;
let oppositePoint:Vertex = null;
this.addVertex( pointB );
// TODO: use radians here
const angles:Array<number> = [ 0.0,
72.0,
144.0,
144.0,
72.0
// 144.0
];
let theta:number = 0.0;
for( var i = 0; i < angles.length; i++ ) {
theta += (180.0 - angles[i]);
pointA = pointB; // center of rotation
pointB = pointB.clone();
pointB.x -= this.edgeLength;
pointB.rotate( theta * (Math.PI/180.0), pointA );
this.addVertex( pointB );
if( i == 2 )
oppositePoint = pointB;
}
// Center and move to desired position
const move:Vertex = new Vertex( (oppositePoint.x - startPoint.x)/2.0,
(oppositePoint.y - startPoint.y)/2.0
);
for( var i = 0; i < this.vertices.length; i++ ) {
this.vertices[i].add( position ).sub( move );
}
this.textureSource.min.x = 77/500.0;
this.textureSource.min.y = 11/460.0;
this.textureSource.max.x = this.textureSource.min.x + 205/500.0;
this.textureSource.max.y = this.textureSource.min.y + 150/460.0;
this.baseBounds = this.getBounds();
this._buildInnerPolygons( this.edgeLength );
this._buildOuterPolygons( this.edgeLength ); // Only call AFTER the inner polygons were created!
};
/**
* @override
*/
clone() : GirihTile {
return new GirihHexagon( this.position.clone(), this.edgeLength ).rotate( this.rotation );
};
private _buildInnerPolygons( edgeLength:number ) : void {
// Connect all edges half-the-way
const innerTile:Polygon = new Polygon();
innerTile.addVertex( this.vertices[0].clone().scale( 0.5, this.vertices[1] ) );
innerTile.addVertex( this.vertices[1].clone().scale( 0.5, this.vertices[2] ) );
// Compute the next inner polygon vertex by the intersection of two circles
const circleA:Circle = new Circle( innerTile.vertices[1], innerTile.vertices[0].distance(innerTile.vertices[1]) );
const circleB :Circle= new Circle( this.vertices[2].clone().scale( 0.5, this.vertices[3] ), circleA.radius );
// TODO: the following piece of code occurs exactly four times.
// -> refactor! (DRY)
// There is definitely an intersection
let intersection:Line|null = circleA.circleIntersection( circleB );
// The intersection is definitely not empty (by construction)
// One of the two points is inside the tile, the other is outside.
// Locate the inside point.
// Use the point that is closer to the center
if( this.position.distance(intersection.a) < this.position.distance(intersection.b) ) innerTile.addVertex(intersection.a);
else innerTile.addVertex(intersection.b);
innerTile.addVertex( circleB.center.clone() );
// var i = 3;
// Move circles
circleA.center = circleB.center;
circleB.center = this.vertices[3].clone().scale( 0.5, this.vertices[4] );
intersection = circleA.circleIntersection( circleB );
// The intersection is definitely not empty (by construction)
// There are two points again (one inside, one outside the tile)
// Use the point that is closer to the center
if( this.position.distance(intersection.a) < this.position.distance(intersection.b) ) innerTile.addVertex(intersection.a);
else innerTile.addVertex(intersection.b);
innerTile.addVertex( circleB.center.clone() );
innerTile.addVertex( this.vertices[4].clone().scale( 0.5, this.vertices[5] ) );
// Move circles
circleA.center = innerTile.vertices[ innerTile.vertices.length-1 ];
circleB.center = this.vertices[5].clone().scale( 0.5, this.vertices[0] );
intersection = circleA.circleIntersection( circleB );
// The intersection is definitely not empty (by construction)
// There are two points again (one inside, one outside the tile)
// Use the point that is closer to the center
if( this.position.distance(intersection.a) < this.position.distance(intersection.b) ) innerTile.addVertex(intersection.a);
else innerTile.addVertex(intersection.b);
innerTile.addVertex( circleB.center.clone() );
// Move circles
circleA.center = innerTile.vertices[ innerTile.vertices.length-1 ];
circleB.center = innerTile.vertices[ 0 ];
intersection = circleA.circleIntersection( circleB );
// The intersection is definitely not empty (by construction)
// There are two points again (one inside, one outside the tile)
// Use the point that is closer to the center
if( this.position.distance(intersection.a) < this.position.distance(intersection.b) ) innerTile.addVertex(intersection.a);
else innerTile.addVertex(intersection.b);
innerTile.addVertex( circleB.center.clone() );
this.innerTilePolygons.push( innerTile );
};
private _buildOuterPolygons( edgeLength:number ) : void {
// First add the two triangles at the 'ends' of the shape.
const indicesA:Array<number> = [ 0, 3 ]; // 6:2
const indicesB:Array<number> = [ 0, 5 ]; // 10:2
for( var i = 0; i < indicesA.length; i++ ) {
const indexA:number = indicesA[i];
const indexB:number = indicesB[i];
// The triangle
const outerTileX:Polygon = new Polygon();
outerTileX.addVertex( this.getVertexAt(indexA+1).clone() );
outerTileX.addVertex( this.innerTilePolygons[0].getVertexAt(indexB).clone() );
outerTileX.addVertex( this.innerTilePolygons[0].getVertexAt(indexB+1).clone() );
this.outerTilePolygons.push( outerTileX );
// The first 'kite'
const outerTileY:Polygon = new Polygon();
outerTileY.addVertex( this.getVertexAt(indexA+2).clone() );
outerTileY.addVertex( this.innerTilePolygons[0].getVertexAt(indexB+1).clone() );
outerTileY.addVertex( this.innerTilePolygons[0].getVertexAt(indexB+2).clone() );
outerTileY.addVertex( this.innerTilePolygons[0].getVertexAt(indexB+3).clone() );
this.outerTilePolygons.push( outerTileY );
// The second 'kite'
const outerTileZ:Polygon = new Polygon();
outerTileZ.addVertex( this.getVertexAt(indexA+3).clone() );
outerTileZ.addVertex( this.innerTilePolygons[0].getVertexAt(indexB+3).clone() );
outerTileZ.addVertex( this.innerTilePolygons[0].getVertexAt(indexB+4).clone() );
outerTileZ.addVertex( this.innerTilePolygons[0].getVertexAt(indexB+5).clone() );
this.outerTilePolygons.push( outerTileZ );
}
};
}