Skip to content

Instantly share code, notes, and snippets.

@troughton
Forked from dribnet/.block
Last active October 6, 2016 03:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save troughton/aeaf2bd277140d93f0d4a8f024db6300 to your computer and use it in GitHub Desktop.
Save troughton/aeaf2bd277140d93f0d4a8f024db6300 to your computer and use it in GitHub Desktop.
Feral – A Parametric Font
license: mit
// note: this file is poorly named - it can generally be ignored.
// helper functions below for supporting blocks/purview
function saveBlocksImages(doZoom) {
if(doZoom == null) {
doZoom = false;
}
// generate 960x500 preview.jpg of entire canvas
// TODO: should this be recycled?
var offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = 960;
offscreenCanvas.height = 500;
var context = offscreenCanvas.getContext('2d');
// background is flat white
context.fillStyle="#FFFFFF";
context.fillRect(0, 0, 960, 500);
context.drawImage(this.canvas, 0, 0, 960, 500);
// save to browser
var downloadMime = 'image/octet-stream';
var imageData = offscreenCanvas.toDataURL('image/jpeg');
imageData = imageData.replace('image/jpeg', downloadMime);
p5.prototype.downloadFile(imageData, 'preview.jpg', 'jpg');
// generate 230x120 thumbnail.png centered on mouse
offscreenCanvas.width = 230;
offscreenCanvas.height = 120;
// background is flat white
context = offscreenCanvas.getContext('2d');
context.fillStyle="#FFFFFF";
context.fillRect(0, 0, 230, 120);
if(doZoom) {
// pixelDensity does the right thing on retina displays
var pd = this._pixelDensity;
var sx = pd * mouseX - pd * 230/2;
var sy = pd * mouseY - pd * 120/2;
var sw = pd * 230;
var sh = pd * 120;
// bounds checking - just displace if necessary
if (sx < 0) {
sx = 0;
}
if (sx > this.canvas.width - sw) {
sx = this.canvas.width - sw;
}
if (sy < 0) {
sy = 0;
}
if (sy > this.canvas.height - sh) {
sy = this.canvas.height - sh;
}
// save to browser
context.drawImage(this.canvas, sx, sy, sw, sh, 0, 0, 230, 120);
}
else {
// now scaledown
var full_width = this.canvas.width;
var full_height = this.canvas.height;
context.drawImage(this.canvas, 0, 0, full_width, full_height, 0, 0, 230, 120);
}
imageData = offscreenCanvas.toDataURL('image/png');
imageData = imageData.replace('image/png', downloadMime);
p5.prototype.downloadFile(imageData, 'thumbnail.png', 'png');
}

Feral – A Parametric Font

This font was created under a very specific set of constraints: every letter is composed of exactly three quarter-circle arcs, which may be scaled, translated, and rotated. In early experimentations I found that I could discernibly represent any letter using three arcs, and I very much liked how it looked.

The result is graceful, agile, and a little wild; the Es read as clawmarks across the page. The strokes are fairly light and the colour palette is on the warmer side of grey; the light on dark indicates a quiet elegance that's reinforced by the smooth curves. I chose "feral" as the word for my preview image, as it encapsulated the wildness and elegance in both the word itself and how the letterforms appear when forming that word.

For the animation in this version, the individual components translate, scale, then rotate into place for the new letter; after tweaking the parameters for when each phase would start and end, I've very happy with the end result. In particular, I think the final 'flick' as the arcs rotate into place has a visual verve that's very appealing.

The sketch will go into presentation mode after 30 seconds of no keyboard input; or, if you press any letter or number on the keyboard the sketch will transition to that letter. In presentation mode, the sketch changes between random letters; it's a great way to see some of the possible diverse and interesting transitions.

<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.2/p5.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.2/addons/p5.dom.js"></script>
<script language="javascript" type="text/javascript" src=".purview_helper.js"></script>
<script language="javascript" type="text/javascript" src="sketch.js"></script>
<style>
body { padding: 0; margin: 0; }
.inner { position: absolute; }
#controls {
font: 300 12px "San Francisco", "Helvetica Neue", sans-serif;
padding: 0;
margin: 0;
background: #f0f0f0;
opacity: 0.7;
-webkit-transition: opacity 0.2s ease;
-moz-transition: opacity 0.2s ease;
-o-transition: opacity 0.2s ease;
-ms-transition: opacity 0.2s ease;
}
#controls:hover { opacity: 0.9; }
</style>
</head>
<body style="background-color:white">
<div class="outer">
<div class="inner">
<div id="canvasContainer"></div>
</div>
<div class="inner" id="controls" height="500px">
</div>
</div>
</body>
{
"A":
[
{
"X": 0.28,
"Y": 0.29,
"Scale": 0.68,
"Rotation": 0
},
{
"X": 0.93,
"Y": 0.32,
"Scale": 0.67,
"Rotation": 1.75929188601028
},
{
"X": 0.57,
"Y": 0.66,
"Scale": 0.32,
"Rotation": 4.02123859659494
}
],
"B":
[
{
"X": 0.5,
"Y": 0.32,
"Scale": 0.47,
"Rotation": 5.21504380495906
},
{
"X": 0.47,
"Y": 0.45,
"Scale": 0.51,
"Rotation": 5.46637121724624
},
{
"X": 0.26,
"Y": 0.39,
"Scale": 0.61,
"Rotation": 5.40353936417444
}
],
"C":
[
{
"X": 0.5,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 3.45575191894877
},
{
"X": 0.5,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 1.13097335529233
},
{
"X": 0.5,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 2.57610597594363
}
],
"D":
[
{
"X": 0.5,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 0
},
{
"X": 0.5,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 4.71238898038469
},
{
"X": 0.15,
"Y": 0.5,
"Scale": 0.77,
"Rotation": 5.46637121724624
}
],
"E":
[
{
"X": 0.5,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 4.02123859659494
},
{
"X": 0.5,
"Y": 0.76,
"Scale": 0.5,
"Rotation": 4.20973415581032
},
{
"X": 0.5,
"Y": 0.61,
"Scale": 0.5,
"Rotation": 4.14690230273853
}
],
"F":
[
{
"X": 0.05,
"Y": 0.5,
"Scale": 0.9,
"Rotation": 5.59203492338983
},
{
"X": 0.6,
"Y": 0.45,
"Scale": 0.5,
"Rotation": 3.70707933123596
},
{
"X": 0.61,
"Y": 0.61,
"Scale": 0.43,
"Rotation": 3.76991118430775
}
],
"G":
[
{
"X": 0.5,
"Y": 0.47,
"Scale": 0.61,
"Rotation": 1.44513262065131
},
{
"X": 0.48,
"Y": 0.5,
"Scale": 0.61,
"Rotation": 3.58141562509236
},
{
"X": 0.51,
"Y": 0.7,
"Scale": 0.30,
"Rotation": 4.71238898038469
}
],
"H":
[
{
"X": 0.01,
"Y": 0.5,
"Scale": 0.78,
"Rotation": 5.59203492338983
},
{
"X": 1,
"Y": 0.55,
"Scale": 0.82,
"Rotation": 2.38761041672824
},
{
"X": 0.5,
"Y": 0.73,
"Scale": 0.5,
"Rotation": 3.89557489045134
}
],
"I":
[
{
"X": 0.81,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 2.51327412287183
},
{
"X": 0.62,
"Y": 0.43,
"Scale": 0.28,
"Rotation": 3.89557489045134
},
{
"X": 0.61,
"Y": 0.78,
"Scale": 0.26,
"Rotation": 3.83274303737955
}
],
"J":
[
{
"X": 0.76,
"Y": 0.46,
"Scale": 0.4,
"Rotation": 2.19911485751286
},
{
"X": 0.62,
"Y": 0.43,
"Scale": 0.32,
"Rotation": 3.83274303737955
},
{
"X": 0.48,
"Y": 0.68,
"Scale": 0.36,
"Rotation": 6.22035345410779
}
],
"K":
[
{
"X": 0.16,
"Y": 0.5,
"Scale": 0.66,
"Rotation": 5.52920307031804
},
{
"X": 0.47,
"Y": 0.27,
"Scale": 0.4,
"Rotation": 0
},
{
"X": 0.5,
"Y": 0.65,
"Scale": 0.36,
"Rotation": 4.5867252742411
}
],
"L":
[
{
"X": 0.11,
"Y": 0.52,
"Scale": 0.54,
"Rotation": 5.52920307031804
},
{
"X": 0.39,
"Y": 0.59,
"Scale": 0.28,
"Rotation": 0.942477796076938
},
{
"X": 0.55,
"Y": 0.82,
"Scale": 0.28,
"Rotation": 4.08407044966673
}
],
"M":
[
{
"X": 0.07,
"Y": 0.36,
"Scale": 0.66,
"Rotation": 6.0318578948924
},
{
"X": 1,
"Y": 0.37,
"Scale": 0.66,
"Rotation": 1.82212373908208
},
{
"X": 0.53,
"Y": 0.15,
"Scale": 0.47,
"Rotation": 0.691150383789755
}
],
"N":
[
{
"X": 0.07,
"Y": 0.36,
"Scale": 0.66,
"Rotation": 5.59203492338983
},
{
"X": 0.94,
"Y": 0.37,
"Scale": 0.66,
"Rotation": 2.32477856365645
},
{
"X": 0.7,
"Y": 0.25,
"Scale": 0.62,
"Rotation": 1.69646003293849
}
],
"O":
[
{
"X": 0.68,
"Y": 0.5,
"Scale": 0.78,
"Rotation": 2.07345115136926
},
{
"X": 0.3,
"Y": 0.51,
"Scale": 0.73,
"Rotation": 5.71769862953342
},
{
"X": 0.48,
"Y": 0.46,
"Scale": 0.42,
"Rotation": 3.83274303737955
}
],
"P":
[
{
"X": 0.14,
"Y": 0.43,
"Scale": 0.82,
"Rotation": 5.59203492338983
},
{
"X": 0.6,
"Y": 0.33,
"Scale": 0.39,
"Rotation": 3.76991118430775
},
{
"X": 0.5,
"Y": 0.23,
"Scale": 0.49,
"Rotation": 6.22035345410779
}
],
"Q":
[
{
"X": 0.6,
"Y": 0.52,
"Scale": 1,
"Rotation": 2.82743338823081
},
{
"X": 0.2,
"Y": 0.31,
"Scale": 1,
"Rotation": 6.0318578948924
},
{
"X": 0.43,
"Y": 0.85,
"Scale": 0.32,
"Rotation": 4.5238934211693
}
],
"R":
[
{
"X": 0,
"Y": 0.5,
"Scale": 1,
"Rotation": 5.52920307031804
},
{
"X": 0.28,
"Y": 0.45,
"Scale": 0.7,
"Rotation": 5.15221195188726
},
{
"X": 0.47,
"Y": 0.82,
"Scale": 0.5,
"Rotation": 4.71238898038469
}
],
"S":
[
{
"X": 0.65,
"Y": 0.36,
"Scale": 0.5,
"Rotation": 3.76991118430775
},
{
"X": 0.65,
"Y": 0.32,
"Scale": 0.48,
"Rotation": 1.88495559215388
},
{
"X": 0.49,
"Y": 0.65,
"Scale": 0.5,
"Rotation": 0.502654824574367
}
],
"T":
[
{
"X": 0.5,
"Y": 0.5,
"Scale": 0.82,
"Rotation": 3.95840674352314
},
{
"X": 0.65,
"Y": 0.3,
"Scale": 0.5,
"Rotation": 2.51327412287183
},
{
"X": 0.23,
"Y": 0.57,
"Scale": 0.5,
"Rotation": 5.65486677646163
}
],
"U":
[
{
"X": 0.61,
"Y": 0.53,
"Scale": 0.5,
"Rotation": 2.26194671058465
},
{
"X": 0.47,
"Y": 0.5,
"Scale": 0.79,
"Rotation": 6.0946897479642
},
{
"X": 0.66,
"Y": 0.66,
"Scale": 0.5,
"Rotation": 1.82212373908208
}
],
"V":
[
{
"X": 0.61,
"Y": 0.53,
"Scale": 0.5,
"Rotation": 2.26194671058465
},
{
"X": 1,
"Y": 0.83,
"Scale": 0.79,
"Rotation": 2.95309709437441
},
{
"X": 0.66,
"Y": 0.66,
"Scale": 0.5,
"Rotation": 1.63362817986669
}
],
"W":
[
{
"X": 1,
"Y": 0.75,
"Scale": 0.75,
"Rotation": 3.078760800518
},
{
"X": 0.51,
"Y": 0.95,
"Scale": 0.41,
"Rotation": 3.83274303737955
},
{
"X": 0,
"Y": 0.73,
"Scale": 0.74,
"Rotation": 4.90088453960008
}
],
"X":
[
{
"X": 0.29,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 0
},
{
"X": 0.24,
"Y": 0.75,
"Scale": 0.77,
"Rotation": 4.96371639267187
},
{
"X": 0.79,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 3.14159265358979
}
],
"Y":
[
{
"X": 0.29,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 0
},
{
"X": 0.64,
"Y": 0.25,
"Scale": 0.47,
"Rotation": 2.01061929829747
},
{
"X": 0.79,
"Y": 0.5,
"Scale": 0.5,
"Rotation": 3.14159265358979
}
],
"Z":
[
{
"X": 0.5,
"Y": 0.1,
"Scale": 0.5,
"Rotation": 0.691150383789755
},
{
"X": 0.71,
"Y": 0.65,
"Scale": 0.7,
"Rotation": 3.0159289474462
},
{
"X": 0.5,
"Y": 0.89,
"Scale": 0.5,
"Rotation": 4.02123859659494
}
],
"1":
[
{
"X": 0.4,
"Y": 0.16,
"Scale": 0.31,
"Rotation": 0
},
{
"X": 0.78,
"Y": 0.47,
"Scale": 0.67,
"Rotation": 2.38761041672824
},
{
"X": 0.53,
"Y": 0.86,
"Scale": 0.32,
"Rotation": 4.02123859659494
}
],
"2":
[
{
"X": 0.47,
"Y": 0.43,
"Scale": 0.5,
"Rotation": 5.96902604182061
},
{
"X": 0.59,
"Y": 0.43,
"Scale": 0.28,
"Rotation": 4.14690230273853
},
{
"X": 0.6,
"Y": 0.85,
"Scale": 0.38,
"Rotation": 3.83274303737955
}
],
"3":
[
{
"X": 0.55,
"Y": 0.32,
"Scale": 0.46,
"Rotation": 4.71238898038469
},
{
"X": 0.58,
"Y": 0.17,
"Scale": 0.47,
"Rotation": 0.502654824574367
},
{
"X": 0.4,
"Y": 0.37,
"Scale": 0.64,
"Rotation": 0
}
],
"4":
[
{
"X": 0.38,
"Y": 0.1,
"Scale": 0.5,
"Rotation": 0.188495559215388
},
{
"X": 0.93,
"Y": 0.46,
"Scale": 0.82,
"Rotation": 2.26194671058465
},
{
"X": 0.54,
"Y": 0.19,
"Scale": 0.45,
"Rotation": 0.942477796076938
}
],
"5":
[
{
"X": 0.61,
"Y": 0.03,
"Scale": 0.46,
"Rotation": 0.879645943005142
},
{
"X": 0.35,
"Y": 0.27,
"Scale": 0.23,
"Rotation": 5.34070751110265
},
{
"X": 0.32,
"Y": 0.58,
"Scale": 0.57,
"Rotation": 5.21504380495906
}
],
"6":
[
{
"X": 0.79,
"Y": 0.41,
"Scale": 0.98,
"Rotation": 2.45044226980004
},
{
"X": 0.4,
"Y": 0.58,
"Scale": 0.47,
"Rotation": 4.20973415581032
},
{
"X": 0.32,
"Y": 0.46,
"Scale": 0.5,
"Rotation": 6.0318578948924
}
],
"7":
[
{
"X": 0.5,
"Y": 0,
"Scale": 0.61,
"Rotation": 0.691150383789755
},
{
"X": 0.82,
"Y": 0.63,
"Scale": 0.8,
"Rotation": 2.82743338823081
},
{
"X": 0.46,
"Y": 0.61,
"Scale": 0.32,
"Rotation": 3.83274303737955
}
],
"8":
[
{
"X": 0.94,
"Y": 0.31,
"Scale": 0.78,
"Rotation": 2.13628300444106
},
{
"X": 0.3,
"Y": 0.29,
"Scale": 0.73,
"Rotation": 5.71769862953342
},
{
"X": 0.63,
"Y": 0.5,
"Scale": 0.32,
"Rotation": 0.942477796076938
}
],
"9":
[
{
"X": 0.31,
"Y": 0.41,
"Scale": 0.98,
"Rotation": 5.46637121724624
},
{
"X": 0.71,
"Y": 0.31,
"Scale": 0.47,
"Rotation": 3.0159289474462
},
{
"X": 0.65,
"Y": 0.14,
"Scale": 0.52,
"Rotation": 1.00530964914873
}
],
"0":
[
{
"X": 0.75,
"Y": 0.5,
"Scale": 0.78,
"Rotation": 2.32477856365645
},
{
"X": 0.23,
"Y": 0.52,
"Scale": 0.82,
"Rotation": 5.46637121724624
},
{
"X": 0.66,
"Y": 0.34,
"Scale": 0.57,
"Rotation": 1.5707963267949
}
]
}
//Animation ideas:
//- un-draw previous stroke (slowly decrease the arc length of old while increasing new)
//- three-stage move into place, scale, rotate
var main_canvas;
var canvasWidth = 960;
var canvasHeight = 500;
var letters;
var sliders = [];
var animationProgress = 1.0;
var colorFront = [227, 222, 212];
var colorBack = [29, 42, 28];
function preload() {
letters = loadJSON('letters.json');
}
var attributes = { "X" : new Attribute(0, 1, 0.5),
"Y" : new Attribute(0, 1, 0.5),
"Scale" : new Attribute(0, 1, 0.5),
"Rotation" : new Attribute(0, 2 * Math.PI, 0)
}; //Every (arc) component of a letter has a position, scale, and rotation
var previousLetter;
var currentLetter = [new CharacterElement(attributes), new CharacterElement(attributes), new CharacterElement(attributes)]; //there are three components per letter
function Attribute(min, max, defaultValue) {
this.min = min;
this.max = max;
this.defaultValue = defaultValue;
}
function Point(x, y) {
this.x = x;
this.y = y;
}
function BoundingBox(min, max) {
this.min = min;
this.max = max;
}
BoundingBox.forQuarterCircleArc = function(centre, radius, theta) {
// We need to check for the four edges, the point at fromTheta, and the point at toTheta
var maxX = -Infinity, minX = Infinity, maxY = -Infinity, minY = Infinity;
// -pi/4 to pi/4 passes through max x
// pi/4 to 3pi/4 passes through max y
// 3pi/4 to 5pi/4 passes through min x
// 5pi/4 to 7pi/4 passes through min y
if (theta > 1.75 * Math.PI) {
maxX = Math.max(maxX, centre.x + radius);
} else if (theta < 0.75 * PI) {
maxY = Math.max(maxY, centre.y + radius);
} else if (theta < 1.25 * PI) {
minX = Math.min(minX, centre.x - radius);
} else {
minY = Math.min(minY, centre.y - radius);
}
var startTheta = theta;
var endTheta = theta + Math.PI * 0.5;
var startX = cos(startTheta) * radius + centre.x;
var startY = sin(startTheta) * radius + centre.y;
var endX = cos(endTheta) * radius + centre.x;
var endY = sin(endTheta) * radius + centre.y;
minX = Math.min(startX, minX);
minX = Math.min(endX, minX);
maxX = Math.max(startX, maxX);
maxX = Math.max(endX, maxX);
minY = Math.min(startY, minY);
minY = Math.min(endY, minY);
maxY = Math.max(startY, maxY);
maxY = Math.max(endY, maxY);
return new BoundingBox(new Point(minX, minY), new Point(maxX, maxY));
}
BoundingBox.prototype.union = function(boundingBox) {
var box = new BoundingBox(this.min, this.max);
box.min = new Point(Math.min(this.min.x, boundingBox.min.x), Math.min(this.min.y, boundingBox.min.y));
box.max = new Point(Math.max(this.max.x, boundingBox.max.x), Math.max(this.max.y, boundingBox.max.y));
return box;
}
/** Performs a deep copy of the passed elements array; since an element array is more or less the same as a letter, this allows us to copy a letter so e.g. we can make modifications to one of the loaded letters while still being able to revert back to the saved version. */
function copyElements(elements) {
var copied = [];
for (elementIdx in elements) {
var element = new CharacterElement(elements[elementIdx]);
copied.push(element);
}
return copied;
}
/** A CharacterElement wraps a dictionary of attributes. This constructor takes either a dictionary of Attributes or a plain key-value dictionary. */
function CharacterElement(attributes) {
for (key in attributes) {
if (attributes[key].defaultValue != undefined) {
this[key] = attributes[key].defaultValue;
} else {
this[key] = attributes[key];
}
}
}
CharacterElement.prototype.centered = function(offset) {
var normalised = new CharacterElement(this);
normalised["X"] = (this["X"] - offset.x);
normalised["Y"] = (this["Y"] - offset.y);
return normalised;
}
CharacterElement.prototype.bounds = function() {
var x = this["X"];
var y = this["Y"];
var scale = this["Scale"];
var rotation = this["Rotation"];
return BoundingBox.forQuarterCircleArc(new Point(x, y), scale * 0.5, rotation);
}
/** Computes the bounding box for a letter. */
function boundsForElements(elements) {
var boundingBox = new BoundingBox(new Point(Infinity, Infinity), new Point(-Infinity, -Infinity));
for (elementIdx in elements) {
var element = elements[elementIdx];
boundingBox = boundingBox.union(element.bounds());
}
return boundingBox;
}
/** Takes a letter, centres it, and returns the centred letter and its scale (since I couldn't figure out how to scale the components without using p5's scale function. */
function normalisedLetterWithElements(elements) {
var bounds = boundsForElements(elements);
// var xRange = bounds.max.x - bounds.min.x;
var yRange = bounds.max.y - bounds.min.y;
var offset = new Point((bounds.min.x + bounds.max.x - 1) * 0.5, (bounds.min.y + bounds.max.y - 1) * 0.5);
var normalised = [];
for (elementIdx in elements) {
var element = elements[elementIdx];
normalised.push(element.centered(offset));
}
return {'letter': normalised, 'scale': 1.0 / yRange };
}
/** Generate the controls table containing all of the sliders. */
function createControlsTable() {
var controlsDiv = document.getElementById("controls");
var innerText = "<table>";
for (element in currentLetter) {
for (attributeName in attributes) {
var attribute = attributes[attributeName];
innerText += "<tr><td>" + attributeName + "</td><td id=\"" + attributeName + element + " Container\"></td></tr>";
}
}
innerText += "<tr><td>Letter</td><td id=\"selectorContainer\"></td></tr><tr><td></td><td id=\"buttonContainer\"></td></tr></table>";
controlsDiv.innerHTML = innerText;
for (elementIdx in currentLetter) {
var element = currentLetter[elementIdx];
for (attributeName in attributes) {
var attribute = attributes[attributeName];
var slider = createSlider(attribute.min, attribute.max, element[attributeName], (attribute.max - attribute.min) * 0.01);
slider.parent(attributeName + elementIdx + " Container");
slider.attributeName = attributeName;
slider.elementIdx = elementIdx;
sliders.push(slider);
}
}
var sel = createSelect();
for (letter in letters) {
sel.option(letter);
}
sel.changed(letterChangedEvent);
var button = createButton('show data');
button.mousePressed(buttonPressedEvent);
sel.parent("selectorContainer");
button.parent("buttonContainer");
}
/** Update our model whenever a slider changes its value. */
function sliderValueChanged(event) {
var slider = event.target;
var attributeName = slider.attributeName;
var elementIdx = slider.elementIdx;
currentLetter[elementIdx][attributeName].value = slider.value;
}
function readValuesFromSliders(sliders) {
for (sliderIdx in sliders) {
var slider = sliders[sliderIdx];
currentLetter[slider.elementIdx][slider.attributeName] = slider.value();
}
}
function setup () {
// create the drawing canvas, save the canvas element
main_canvas = createCanvas(canvasWidth, canvasHeight);
main_canvas.parent('canvasContainer');
for (letter in letters) {
var elements = letters[letter];
for (elementIdx in elements) {
var element = elements[elementIdx];
element.__proto__ = CharacterElement.prototype; //Since we've loaded from JSON the prototype isn't correctly set.
}
}
// createControlsTable();
changeToLetter("A");
}
function changeToLetter(letter) {
animationProgress = 0.0;
var result = normalisedLetterWithElements(letters[letter]);
previousLetter = currentLetter;
currentLetter = result.letter;
for (sliderIdx in sliders) {
var slider = sliders[sliderIdx];
slider.value(currentLetter[slider.elementIdx][slider.attributeName]);
}
}
function letterChangedEvent(event) {
var item = event.target.value;
changeToLetter(item);
}
function changeToRandomLetter() {
var letterKeys = Object.keys(letters);
var letter;
do { //We don't want to animate back to the same letter.
var index = Math.floor(letterKeys.length * Math.random());
letter = letterKeys[index];
} while (letter == currentLetter);
changeToLetter(letter);
}
function buttonPressedEvent() {
json = JSON.stringify(currentLetter, null, 2);
alert(json);
}
function saturate(x) {
return max(0, min(1, x));
}
/** Interpolates between fromElements and toElements in a custom animation based on percentage. */
function animateCharacterElements(fromElements, toElements, percentage) {
var newElements = [];
var scaledPercentage = percentage * 3.0;
for (index in fromElements) {
var fromElement = fromElements[index];
var toElement = toElements[index];
var animatedElement = new CharacterElement(fromElement);
var translationPercentage = saturate(percentage * 2.5);
var scalePercentage = saturate(percentage * 2.1 - 0.6);
var rotationPercentage = saturate(percentage * 3.6 - 2.1);
animatedElement["X"] = lerp(fromElement["X"], toElement["X"], translationPercentage);
animatedElement["Y"] = lerp(fromElement["Y"], toElement["Y"], translationPercentage);
animatedElement["Scale"] = lerp(fromElement["Scale"], toElement["Scale"], scalePercentage);
animatedElement["Rotation"] = lerp(fromElement["Rotation"], toElement["Rotation"], rotationPercentage);
newElements.push(animatedElement);
}
return newElements;
}
/** Draws an individual part of a letter (e.g. an individual arc). */
function drawFromAttributes(attributes) {
var x = attributes["X"];
var y = attributes["Y"];
var rotation = attributes["Rotation"];
var scale = attributes["Scale"];
{
push();
noFill();
stroke(colorFront);
strokeWeight(0.007);
var fromAngle = 0 + rotation;
var toAngle = HALF_PI + fromAngle;
arc(x, y, scale, scale, fromAngle, toAngle);
pop();
}
}
function drawLetter(letter) {
for (elementIndex in letter) {
var characterElement = letter[elementIndex];
drawFromAttributes(characterElement);
}
}
var timeLastUpdate = 0;
var timeLastKeyPress = 0;
var timeLastAnimationStarted = 0;
function updateAnimation() {
var currentTime = millis();
var elapsedTime = currentTime - timeLastUpdate;
var animationDuration = 800.0;
if (animationProgress == 0.0) {
timeLastAnimationStarted = currentTime;
}
animationProgress = Math.min(1.0, animationProgress + elapsedTime / animationDuration);
timeLastUpdate = currentTime;
//If we've had 30s of keyboard inactivity and we haven't started an animation in the last 4 seconds
if (currentTime - timeLastKeyPress > 30000 && currentTime - timeLastAnimationStarted > 4000) {
changeToRandomLetter();
}
}
function draw () {
updateAnimation();
readValuesFromSliders(sliders);
var minDimension = Math.min(width, height);
translate((width - minDimension) * 0.5, 0);
scale(minDimension, minDimension); //Work in a square area in the centre of the screen.
background(colorBack);
fill(colorFront);
stroke(95, 52, 8);
if (animationProgress < 1.0) {
var letter = animateCharacterElements(previousLetter, currentLetter, animationProgress);
drawLetter(letter);
} else {
drawLetter(currentLetter, 0, animationProgress);
}
}
function keyTyped() {
timeLastKeyPress = millis();
if (key == '!') {
saveBlocksImages();
}
else if (key == '@') {
saveBlocksImages(true);
} else {
var letter = key.toUpperCase();
if (letters[letter] != null) {
changeToLetter(letter);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment