Insert cell
Insert cell
Insert cell
Insert cell
{
const height = width/2;
const zoom = 0.25;
const asp = width / height;
const h = 6.0 * zoom;
const w = asp * h * zoom;
const sc = height / (2*h);
const M = mul(
[1, 0, width/2.0, 0, 1, height/2.0],
[sc, 0, 0, 0, -sc, 0] );
const canvas = DOM.canvas(width, height);
var context = canvas.getContext("2d");

const COLS = ["black"]
for( let i of a_tiling.fillRegionBounds( -w-5.0, -h-5.0, w+5.0, h+5.0 ) ) {
const TT = i.T;
const T = mul( M, TT );

const col = COLS[ a_tiling.getColour( i.t1, i.t2, i.aspect ) + 0 ];

context.fillStyle = "white";
context.strokeStyle = "black";
context.beginPath();
var iii = 0;
for( let v of tile_shape ) {
const P = mul( T, v );
if(iii==0){
context.moveTo( P.x, P.y );
}else{
context.lineTo( P.x, P.y );
}
iii++;
}
var P0 = mul(T,tile_shape[0]);
context.lineTo( P0.x, P0.y );
context.fill();
context.stroke();
}
return canvas
}
Insert cell
// this is the tiling ID shape out of the 81 shapes
tilingID = 15
Insert cell
tiling = new IsohedralTiling( tilingTypes[ 15 ] );
Insert cell
tt_params = tiling.getParameters();
Insert cell
a_tiling = {
let params = tiling.getParameters();
params[ 0 ] = 0;
tiling.setParameters( params );
return(tiling)
}
Insert cell
M_vec = {
var M = [];
for( let i of a_tiling.fillRegionBounds( 0.0, 0.0, 8.0, 5.0 ) ) {
const T = i.T;
const col = a_tiling.getColour( i.t1, i.t2, i.aspect );
M.push(i)
}
return(M)
}
Insert cell
edges = {
// set edges
const edges = [];
for( let idx = 0; idx < a_tiling.numEdgeShapes(); ++idx ) {
var ej = [{ x: 0, y: 0 }, { x: 1, y: 0 }];
edges.push( ej );
}
return(edges)
}
Insert cell
tile_shape = {
// set tile_shape
const tile_shape = [];
for( let i of a_tiling.parts() ) {
const ej = edges[i.id];
let cur = i.rev ? (ej.length-2) : 1;
const inc = i.rev ? -1 : 1;

for( let idx = 0; idx < ej.length - 1; ++idx ) {
tile_shape.push( mul( i.T, ej[cur] ) );
cur += inc;
}
}
return tile_shape
}
Insert cell
Insert cell
Insert cell
EdgeShape = {return {
J : 10001,
U : 10002,
S : 10003,
I : 10004,
};}
Insert cell
numTypes = 81;
Insert cell
function mul( A, B )
{
if( B.hasOwnProperty( 'x' ) ) {
// Matrix * Point
return {
x : A[0]*B.x + A[1]*B.y + A[2],
y : A[3]*B.x + A[4]*B.y + A[5] };
} else {
// Matrix * Matrix
return [A[0]*B[0] + A[1]*B[3],
A[0]*B[1] + A[1]*B[4],
A[0]*B[2] + A[1]*B[5] + A[2],

A[3]*B[0] + A[4]*B[3],
A[3]*B[1] + A[4]*B[4],
A[3]*B[2] + A[4]*B[5] + A[5]];
}
};
Insert cell
function matchSeg( p, q )
{
return [q.x-p.x, p.y-q.y, p.x, q.y-p.y, q.x-p.x, p.y];
};
Insert cell
tilingTypes = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 61, 62, 64, 66, 67, 68, 69, 71, 72, 73, 74, 76, 77, 78, 79, 81, 82, 83, 84, 85, 86, 88, 90, 91, 93
];

Insert cell
Insert cell
function makePoint( coeffs, offs, params )
{
let ret = { x : 0.0, y : 0.0 }

for( let i = 0; i < params.length; ++i ) {
ret.x += coeffs[offs+i] * params[i];
ret.y += coeffs[offs+params.length+i] * params[i];
}

return ret;
};
Insert cell

function makeMatrix( coeffs, offs, params )
{
let ret = []

for( let row = 0; row < 2; ++row ) {
for( let col = 0; col < 3; ++col ) {
let val = 0.0;
for( let idx = 0; idx < params.length; ++idx ) {
val += coeffs[offs+idx] * params[idx];
}
ret.push( val );
offs += params.length;
}
}

return ret;
};
Insert cell
M_orients = [
[1.0, 0.0, 0.0, 0.0, 1.0, 0.0], // IDENTITY
[-1.0, 0.0, 1.0, 0.0, -1.0, 0.0], // ROT
[-1.0, 0.0, 1.0, 0.0, 1.0, 0.0], // FLIP
[1.0, 0.0, 0.0, 0.0, -1.0, 0.0] // ROFL
];
Insert cell
TSPI_U = [
[0.5, 0.0, 0.0, 0.0, 0.5, 0.0],
[-0.5, 0.0, 1.0, 0.0, 0.5, 0.0]
];
Insert cell
TSPI_S = [
[0.5, 0.0, 0.0, 0.0, 0.5, 0.0],
[-0.5, 0.0, 1.0, 0.0, -0.5, 0.0]
];
Insert cell
class IsohedralTiling
{
constructor( tp )
{
this.reset( tp );
}

reset( tp )
{
this.tiling_type = tp;
this.ttd = tiling_type_data[ tp ];
this.parameters = this.ttd.default_params.slice( 0 );
this.parameters.push( 1.0 );
this.recompute();
}

recompute()
{
const ntv = this.numVertices();
const np = this.numParameters();
const na = this.numAspects();

// Recompute tiling vertex locations.
this.verts = [];
for( let idx = 0; idx < ntv; ++idx ) {
this.verts.push( makePoint( this.ttd.vertex_coeffs,
idx * (2 * (np + 1)), this.parameters ) );
}

// Recompute edge transforms and reversals from orientation information.
this.reversals = [];
this.edges = []
for( let idx = 0; idx < ntv; ++idx ) {
const fl = this.ttd.edge_orientations[2*idx];
const ro = this.ttd.edge_orientations[2*idx+1];
this.reversals.push( fl != ro );
this.edges.push(
mul( matchSeg( this.verts[idx], this.verts[(idx+1)%ntv] ),
M_orients[2*fl+ro] ) );
}

// Recompute aspect xforms.
this.aspects = []
for( let idx = 0; idx < na; ++idx ) {
this.aspects.push(
makeMatrix( this.ttd.aspect_coeffs, 6*(np+1)*idx,
this.parameters ) );
}
// Recompute translation vectors.
this.t1 = makePoint(
this.ttd.translation_coeffs, 0, this.parameters );
this.t2 = makePoint(
this.ttd.translation_coeffs, 2*(np+1), this.parameters );
}

getTilingType()
{
return this.tiling_type;
}

numParameters()
{
return this.ttd.num_params;
}

setParameters( arr )
{
if( arr.length == (this.parameters.length-1) ) {
this.parameters = arr.slice( 0 );
this.parameters.push( 1.0 );
this.recompute();
}
}

getParameters()
{
return this.parameters.slice( 0, -1 );
}

numEdgeShapes()
{
return this.ttd.num_edge_shapes;
}

getEdgeShape( idx )
{
return this.ttd.edge_shapes[ idx ];
}

* shape()
{
for( let idx = 0; idx < this.numVertices(); ++idx ) {
const an_id = this.ttd.edge_shape_ids[idx];

yield {
T : this.edges[idx],
id : an_id,
shape : this.ttd.edge_shapes[ an_id ],
rev : this.reversals[ idx ]
};
}
}

* parts()
{
for( let idx = 0; idx < this.numVertices(); ++idx ) {
const an_id = this.ttd.edge_shape_ids[idx];
const shp = this.ttd.edge_shapes[an_id];

if( (shp == EdgeShape.J) || (shp == EdgeShape.I) ) {
yield {
T : this.edges[idx],
id : an_id,
shape : shp,
rev : this.reversals[ idx ],
second : false
};
} else {
const indices = this.reversals[idx] ? [1,0] : [0,1];
const Ms = (shp == EdgeShape.U) ? TSPI_U : TSPI_S;

yield {
T : mul( this.edges[idx], Ms[indices[0]] ),
id : an_id,
shape : shp,
rev : false,
second : false
};
yield {
T : mul( this.edges[idx], Ms[indices[1]] ),
id : an_id,
shape : shp,
rev : true,
second : true
};
}
}
}

numVertices()
{
return this.ttd.num_vertices;
}

getVertex( idx )
{
return { ...this.verts[ idx ] };
}

vertices()
{
return this.verts.map( v => ({ ...v }) );
}

numAspects()
{
return this.ttd.num_aspects;
}
getAspectTransform( idx )
{
return [ ...this.aspects[ idx ] ];
}

getT1()
{
return { ...this.t1 };
}

getT2()
{
return { ...this.t2 };
}

* fillRegionBounds( xmin, ymin, xmax, ymax )
{
yield* this.fillRegionQuad(
{ x : xmin, y : ymin },
{ x : xmax, y : ymin },
{ x : xmax, y : ymax },
{ x : xmin, y : ymax } );
}

* fillRegionQuad( A, B, C, D )
{
const t1 = this.getT1();
const t2 = this.getT2();
const ttd = this.ttd;
const aspects = this.aspects;

function bc( M, p ) {
return {
x : M[0]*p.x + M[1]*p.y,
y : M[2]*p.x + M[3]*p.y };
};

function sampleAtHeight( P, Q, y )
{
const t = (y-P.y)/(Q.y-P.y);
return { x : (1.0-t)*P.x + t*Q.x, y : y };
}

function* doFill( A, B, C, D, do_top )
{
let x1 = A.x;
const dx1 = (D.x-A.x)/(D.y-A.y);
let x2 = B.x;
const dx2 = (C.x-B.x)/(C.y-B.y);
const ymin = A.y;
let ymax = C.y;

if( do_top ) {
ymax = ymax + 1.0;
}

let y = Math.floor( ymin );
while( y < ymax ) {
const yi = Math.trunc( y );
let x = Math.floor( x1 );
while( x < (x2 + 1e-7) ) {
const xi = Math.trunc( x );

for( let asp = 0; asp < ttd.num_aspects; ++asp ) {
let M = aspects[ asp ].slice( 0 );
M[2] += xi*t1.x + yi*t2.x;
M[5] += xi*t1.y + yi*t2.y;

yield {
T : M,
t1 : xi,
t2 : yi,
aspect : asp
};
}

x += 1.0;
}
x1 += dx1;
x2 += dx2;
y += 1.0;
}
}

function* fillFixX( A, B, C, D, do_top )
{
if( A.x > B.x ) {
yield* doFill( B, A, D, C, do_top );
} else {
yield* doFill( A, B, C, D, do_top );
}
}
function* fillFixY( A, B, C, D, do_top )
{
if( A.y > C.y ) {
yield* doFill( C, D, A, B, do_top );
} else {
yield* doFill( A, B, C, D, do_top );
}
}

const det = 1.0 / (t1.x*t2.y-t2.x*t1.y);
const Mbc = [ t2.y * det, -t2.x * det, -t1.y * det, t1.x * det ];

let pts = [ bc( Mbc, A ), bc( Mbc, B ), bc( Mbc, C ), bc( Mbc, D ) ];

if( det < 0.0 ) {
let tmp = pts[1];
pts[1] = pts[3];
pts[3] = tmp;
}

if( Math.abs( pts[0].y - pts[1].y ) < 1e-7 ) {
yield* fillFixY( pts[0], pts[1], pts[2], pts[3], true );
} else if( Math.abs( pts[1].y - pts[2].y ) < 1e-7 ) {
yield* fillFixY( pts[1], pts[2], pts[3], pts[0], true );
} else {
let lowest = 0;
for( let idx = 1; idx < 4; ++idx ) {
if( pts[idx].y < pts[lowest].y ) {
lowest = idx;
}
}

let bottom = pts[lowest];
let left = pts[(lowest+1)%4];
let top = pts[(lowest+2)%4];
let right = pts[(lowest+3)%4];

if( left.x > right.x ) {
let tmp = left;
left = right;
right = tmp;
}

if( left.y < right.y ) {
const r1 = sampleAtHeight( bottom, right, left.y );
const l2 = sampleAtHeight( left, top, right.y );
yield* fillFixX( bottom, bottom, r1, left, false );
yield* fillFixX( left, r1, right, l2, false );
yield* fillFixX( l2, right, top, top, true );
} else {
const l1 = sampleAtHeight( bottom, left, right.y );
const r2 = sampleAtHeight( right, top, left.y );
yield* fillFixX( bottom, bottom, right, l1, false );
yield* fillFixX( l1, right, r2, left, false );
yield* fillFixX( left, r2, top, top, true );
}
}
}
getColour( a, b, asp )
{
const clrg = this.ttd.colouring;
const nc = clrg[18];

let mt1 = a % nc;
if( mt1 < 0 ) {
mt1 += nc;
}
let mt2 = b % nc;
if( mt2 < 0 ) {
mt2 += nc;
}
let col = clrg[asp];

for( let idx = 0; idx < mt1; ++idx ) {
col = clrg[12+col];
}
for( let idx = 0; idx < mt2; ++idx ) {
col = clrg[15+col];
}

return col;
}
};
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more