I have been busy but I am back with some more suggestions to improve your code.
We will start here
List<D> d = new ArrayList<D>();
List<O> o = new ArrayList<O>();
This is fine if you want to display ‘dodo’ but what about words using different characters, you would need a list for each and every character that you might want to display. This is not practical especially if we want both uppercase and lowercase letters and maybe special characters e.g. ? & % #
.
Classes and objects are the core of object orientation (OO) but probably the most powerful feature of OO is inheritance. Unfortunately, it is probably the most misunderstood / misused feature of OO.
I am going to show how to use inheritance to solve the problem of multiple lists.
Let’s consider the two classes O
and D
what do they do? Well, they both draw a glyph. Now Wikipedia says a glyph is any kind of purposeful mark so will cover all letters, numbers and special characters. It means we can say that ‘D’ is a kind of glyph and ‘O’ is also a kind of glyph. So we will create 3 classes like this
abstract class Glyph { /* class attributes and methods */ }
class O extends Glyph { /* class attributes and methods */ }
class D extends Glyph { /* class attributes and methods */ }
Glyph is called the parent class and O and D child classes – hence the term inheritance. A child class incorporates all the attribute and methods from the parent class and adds attributes and methods specific to itself. So, when creating the classes any attributes and methods common to all characters are stored in the Glyph class and those specific to a character are stored in the appropriate child class.
So back to the lists, instead of one list for each character type we can have one list to store all character types.
List<Glyph> glyphs = new ArrayList<Glyph>();
And we can store characters with
glyphs.add(new O(…));
glyphs.add(new D(…));
glyphs.add(new D(…));
...
To draw any character, it is simply a matter of creating a new class that extends Glyph
. Please note that the keyword abstract
does not signify a parent class it has a different meaning altogether which I can explain in another reply if you wish.
A similar situation occurs inside the D class
class D {
List<Arc> arcs = new ArrayList<Arc>();
List<Line> lines = new ArrayList<Line>();
Arcs and lines are simple drawing elements used in combination to create a glyph but what if we wanted a Bezier curve element? Same problem same solution but this time I have called the parent class Element
and the classes Arc
and Line
inherit from it.
I should point out that this description simply scratches the surface of OO.
This description would be of little use without some working code to demonstrate it so I have created a sketch which utilises inheritance. The video shows the sketch output and below it the full source code.
Sketch code
import java.util.ArrayList;
import java.util.List;
int TIMESTAMP;
List<Glyph> glyphs = new ArrayList<Glyph>();
void setup() {
size(560, 360);
int st = 1000, dt = 600, gw = 100, gh = 100, py = 20;
glyphs.add(new Q(20, py, gw, gh, st));
glyphs.add(new U(20 + gw, py, gw, gh, st + dt));
glyphs.add(new A(20 + gw * 2, py, gw, gh, st + dt * 2));
glyphs.add(new R(20 + gw * 3, py, gw, gh, st + dt * 3));
glyphs.add(new K(20 + gw * 4, py, gw, gh, st + dt * 4));
st = 4000;
gh = 50;
py = 140;
glyphs.add(new Q(20, py, gw, gh, st));
glyphs.add(new U(20 + gw, py, gw, gh, st + dt));
glyphs.add(new A(20 + gw * 2, py, gw, gh, st + dt * 2));
glyphs.add(new R(20 + gw * 3, py, gw, gh, st + dt * 3));
glyphs.add(new K(20 + gw * 4, py, gw, gh, st + dt * 4));
st = 8000;
gw = 60;
gh = 120;
py = 220;
glyphs.add(new Q(40, py, gw, gh, st));
glyphs.add(new U(40 + gw, py, gw, gh, st+dt));
glyphs.add(new A(40 + gw * 2, py, gw, gh, st + dt * 2));
glyphs.add(new R(40 + gw * 3, py, gw, gh, st + dt * 3));
glyphs.add(new K(40 + gw * 4, py, gw, gh, st + dt * 4));
TIMESTAMP = millis(); // This will compensate for long setup times
}
void draw() {
background(0);
for (Glyph g : glyphs) g.update(millis() - TIMESTAMP);
stroke(255);
strokeWeight(10);
for (Glyph g : glyphs) g.render();
}
/**
* Base class for graphic elements used to build up a letter or character.
* At the moment this only covers Arc(s) and Line(s)
*
*/
abstract class Element {
int startTime, duration;
float t = 0; // parametric variable
int foreCol = 0xFFFFFFFF; // line colour
float thickness = 10; // line weight
abstract void render();
/**
* @param startTime sketch time to start animation (milliseconds)
* @param duration time to perform animation (milliseconds)
*/
Element(int startTime, int duration) {
this.startTime = startTime;
this.duration = duration;
}
/**
* Calculate the how much of the element to render
* @param ct current sketch time
*/
void update(int ct) {
if (t < 1) {
if (ct > startTime) t = (ct - startTime) / (float) duration;
t = constrain(t, 0, 1);
}
}
}
class Line extends Element {
float x0, y0, x1, y1;
int dir;
/**
* @param x0 the X position for one end of the line
* @param y0 the Y position for one end of the line
* @param x1 the X position for the other end of the line
* @param y1 the Y position for the other end of the line
* @param dir animation direction (1 = [x0,y0] >> [x1,y1] ; -1 = reverse)
* @param startTime sketch time to start animation (milliseconds)
* @param duration time to perform animation (milliseconds)
*/
Line(float x0, float y0, float x1, float y1, int dir, int startTime, int duration) {
super(startTime, duration);
this.x0 = x0;
this.y0 = y0;
this.x1 = x1;
this.y1 = y1;
this.dir = dir < 0 ? -1 : 1;
}
/**
* Render the arc segment
*/
void render() {
if (t > 0) {
push();
stroke(foreCol);
strokeWeight(thickness);
//float rangeX = x1 - x0, rangeY = y1 - y0;
float dtx = (x1 - x0) * t;
float dty = (y1 - y0) * t;
if (dir > 0) {
line(x0, y0, x0 + dtx, y0 + dty);
} else {
line(x1, y1, x1 - dtx, y1 - dty);
}
pop();
}
}
}
class Arc extends Element {
float px, py, radX, radY;
float angS, angE;
int dir;
float TAU = 2 * PI;
/**
* @param px arc centre
* @param py arc centre
* @param angS the angle the animation starts from (radians)
* @param angE the angle the animation ends at (radians)
* @param dir animation direction (-1 = anti-clockwise ; 1 = clockwise)
* @param startTime sketch time to start animation (milliseconds)
* @param duration time to perform animation (milliseconds)
*/
Arc(float px, float py, float radX, float radY, float angS, float angE, int dir, int startTime, int duration) {
super(startTime, duration);
this.px = px;
this.py = py;
this.radX = radX;
this.radY = radY;
this.angS = normAngle(angS);
this.angE = normAngle(angE);
this.dir = dir < 0 ? -1 : 1;
}
/**
* Converts an angle to its equivalent value in the range 0 - 2Pi
*/
float normAngle(float a) {
while (a < 0) a += TAU;
while (a > TAU) a -= TAU;
return a;
}
/**
* Render the arc segment
*/
void render() {
if (t > 0) {
push();
stroke(foreCol);
strokeWeight(thickness);
noFill();
translate(px, py);
float as = angS, ae = angE, a_t = 0;
if (dir > 0) {
if (as > ae) ae += TAU;
a_t = as + (ae - as) * t;
arc(0, 0, 2*radX, 2*radY, as, a_t);
} else {
if (ae > as) as += TAU;
a_t = as + (ae - as) * t;
arc(0, 0, 2 * radX, 2 * radY, a_t, as);
}
pop();
}
}
}
abstract class Glyph {
float gx, gy, gw, gh;
float startTime;
List<Element> parts = new ArrayList<Element>();
/**
* @param gx top-left corner X position for glyph
* @param gy top-left corner Y position for glyph
* @param gw glyph width to use
* @param gh glyph height to use
* @param startTime time to start drawing glyph
*/
Glyph(float gx, float gy, float gw, float gh, int startTime) {
this.gx = gx;
this.gy = gy;
this.gw = gw;
this.gh = gh;
this.startTime = startTime;
}
void update(int ct) {
if (ct > startTime)
for (Element part : parts) part.update(ct);
}
void render() {
push();
strokeCap(ROUND);
translate(gx, gy);
for (Element part : parts) part.render();
pop();
}
}
class Q extends Glyph {
Q(float gx, float gy, float gw, float gh, int startTime) {
super(gx, gy, gw, gh, startTime);
float cx = 0.5 * gw, cy = 0.5 * gh, rx = 0.35 * gw, ry = 0.4 * gh;
parts.add(new Arc(cx, cy, rx, ry, PApplet.radians(35), PApplet.radians(45), -1, startTime, 800));
parts.add(new Line(cx + 0.15 * gw, cy + 0.2 * gh, cx + 0.35 * gw, cy + 0.4 * gh, 1, startTime + 800, 100));
}
}
class U extends Glyph {
U(float gx, float gy, float gw, float gh, int startTime) {
super(gx, gy, gw, gh, startTime);
float cx = 0.5 * gw, cy = 0.6 * gh, rx = 0.25 * gw, ry = 0.3 * gh;
parts.add(new Arc(cx, cy, rx, ry, PApplet.radians(90), PApplet.radians(180), 1, startTime, 500));
parts.add(new Arc(cx, cy, rx, ry, PApplet.radians(90), PApplet.radians(0), -1, startTime, 500));
parts.add(new Line(cx - rx, 0.1 * gh, cx - rx, 0.6 * gh, -1, startTime + 500, 250));
parts.add(new Line(cx + rx, 0.1 * gh, cx + rx, 0.6 * gh, -1, startTime + 500, 250));
}
}
class A extends Glyph {
A(float gx, float gy, float gw, float gh, int startTime) {
super(gx, gy, gw, gh, startTime);
float x0 = 0.2 * gw, x1 = 0.31 * gw, x2 = 0.5 * gw, x3 = 0.7 * gw, x4 = 0.8 * gw;
float y0 = 0.9 * gh, y1 = 0.6 * gh, y2 = 0.1 * gh;
parts.add(new Line(x0, y0, x2, y2, 1, startTime, 300));
parts.add(new Line(x2, y2, x3, y1, 1, startTime+ 300, 200));
parts.add(new Line(x4, y0, x3, y1, 1, startTime+ 450, 200));
parts.add(new Line(x1, y1, x3, y1, -1, startTime+ 650, 200));
}
}
class R extends Glyph {
R(float gx, float gy, float gw, float gh, int startTime) {
super(gx, gy, gw, gh, startTime);
float x0 = 0.2 * gw, x1 = 0.5 * gw, x2 = 0.55 * gw, x3 = 0.75 * gw;
float y0 = 0.1 * gh, y1 = 0.3 * gh, y2 = 0.5 * gh, y3 = 0.9 * gh;
parts.add(new Line(x0, y0, x2, y0, 1, startTime, 300));
parts.add(new Line(x0, y2, x2, y2, -1, startTime+ 800, 200));
parts.add(new Arc(x2, y1, 0.2f*gw, 0.2f*gh, PApplet.radians(270), PApplet.radians(90), 1, startTime + 300, 500));
parts.add(new Line(x0, y3, x0, y0, 1, startTime+ 700, 200));
parts.add(new Line(x3, y3, x1, y2, 1, startTime+ 700, 100));
}
}
class K extends Glyph {
K(float gx, float gy, float gw, float gh, int startTime) {
super(gx, gy, gw, gh, startTime);
float x0 = 0.2 * gw, x1 = 0.4 * gw, x2 = 0.8 * gw;
float y0 = 0.1 * gh, y1 = 0.43 * gh, y2 = 0.6 * gh, y3 = 0.9 * gh;
parts.add(new Line(x0, y3, x0, y0, 1, startTime, 500));
parts.add(new Line(x0, y2, x2, y0, -1, startTime + 300, 400));
parts.add(new Line(x1, y1, x2, y3, -1, startTime + 700, 200));
}
}