Skip to content

Instantly share code, notes, and snippets.

@jphastings
Created April 11, 2010 19:15
Show Gist options
  • Select an option

  • Save jphastings/362995 to your computer and use it in GitHub Desktop.

Select an option

Save jphastings/362995 to your computer and use it in GitHub Desktop.
How I built Project Prime

Project Prime

See the final piece at http://poetry.byJP.me/projectprime.

Introduction

Project Prime is a collaborative artwork, initiated by Cy Densham based on four of his poems, Chapters I, II, III and IV.

Though they are, perhaps, uninspiring names they were chosen to allow readers (in particular the collaborative artists involved with this work) to form their own interpretations and impressions of the short collections of words within. Over the course of several weeks 44 people submitted a little information about themselves and their impressions of each of the pieces.

Each person is represented in the artwork below by a coloured shape. Each shape's centre is placed on the canvas according to their age, with older people towards the right, and the first people to collaborate nearer the bottom. The colour of the shape shows their preference towards each poem the stronger the primary colour the more they liked that poem; red for Chapter II, green for Chapter III and blue for Chapter IV. Chapter I has is preference shown by transparency, the faster a shape pulses the more it was liked by the person it represents.

Behind the Scenes

The maths behind creating the artwork is a little complex, but it can be explained fairly easily. The age of the collaborator (in seconds) and the time at which they submitted their work are used as the X and Y coordinates (respectively) for the centre of their shape (we'll call these nodes). Using these points a bunch of DeLaunay triangles are generated, allowing me to find a (slightly modified) Voronoi diagram of these points -- in plain english, I find the mid-point of the line joining two nodes and draw another line perpendicular to it, these will form a shape around the node, in a fashion very similar to the first Brillouin Zone in chemistry.

Now I've found the shape of each person's contribution I just need to colour it, the colour is a very simple RGBA (or in this case, ARGB) mapping, where the person's percentage rating of each poem is the percentage of each colour -- Alpha (or transparency), Red, Green and Blue.

It's that simple!

Below is the code I used to generate the SVG used for Project Prime, written in MATLAB. Its not actually perfect and a small amount of hand-tweaking needs to be done afterwards (I never got round to dealing with the edges very well, which is pretty much the only serious challenge in the project's technical side!) -- good luck

clear all;
hold off;
bl = [0 0]; % This should remain
tr = [860 500]; % Size of field
%% CSV Data
data = fopen('data.csv','r');
out = fscanf(data,'%d,%d,%f,%f,%f,%f\n',[6 inf]);
fclose(data);
%% Generating Random test-data
% out = [];
% for n=1:600
% out = [out;rand * 800,rand * 500,rand,rand,rand,rand];
% end
% out = out';
raw_points = out(1:2,:)';
point_colours = out(3:6,:)';
% Scaling to make it pretty!
raw_points(:,1) = log(raw_points(:,1) - min(raw_points(:,1)) + 2e7);
raw_points(:,2) = log(raw_points(:,2) - min(raw_points(:,2)) + 1000);
% Processing
low = min(raw_points);
high = max(raw_points);
points = (raw_points - ones([length(raw_points) 2]) * [low(1) 0;0 low(2)]) * [(tr(1)-bl(1))/(high(1)-low(1)) 0;0 (tr(2)-bl(2))/(high(2)-low(2))] + ones([length(raw_points) 2]) * [bl(1) 0;0 bl(2)];
trios = [];
lines = [];
cells = cell(1,length(points));
plot([bl(1) bl(1) tr(1) tr(1) bl(1)],[bl(2) tr(2) tr(2) bl(2) bl(2)],'k:');
hold on;
% Find good trios
trios = delaunay(points(:,1),points(:,2));
mus = linspace(-6,6,21);
% The list of cells we shouldn't look for fake sites
dont_fake = [];
for trio=trios'
d = trio(1);
e = trio(2);
f = trio(3);
la12 = 0.5;
la13 = 0.5;
la23 = 0.5;
m12 = points(d,1:2) + la12*(points(e,1:2)-points(d,1:2));
m13 = points(d,1:2) + la13*(points(f,1:2)-points(d,1:2));
m23 = points(e,1:2) + la23*(points(f,1:2)-points(e,1:2));
perp12 = [points(e,2) - points(d,2), points(d,1) - points(e,1)];
perp13 = [points(f,2) - points(d,2), points(d,1) - points(f,1)];
perp23 = [points(f,2) - points(e,2), points(e,1) - points(f,1)];
mu12 = (m12(2) * perp13(1) - m13(2) * perp13(1) - m12(1) * perp13(2) + m13(1) * perp13(2))/(perp12(1) * perp13(2) - perp12(2) * perp13(1));
% These lines will produce helpers in the MATLAB plot output, just for debugging really
% a = (m12'*ones([1 21]) + (mus' * perp12)')';
% b = (m13'*ones([1 21]) + (mus' * perp13)')';
% c = (m23'*ones([1 21]) + (mus' * perp23)')';
% plot(a(:,1),a(:,2),'r-');
% plot(m12(1),m12(2),'rx');
% plot(b(:,1),b(:,2),'g-');
% plot(m13(1),m13(2),'gx');
% plot(c(:,1),c(:,2),'b-');
% plot(m23(1),m23(2),'bx');
C = m12 + mu12*perp12;
% Check to find the point closest to the line: the calculated point, or
% the two points where the perpendicular hits the 4 edges
newCs = [];
for t=[m12 perp12;m13 perp13;m23 perp23]'
m = t(1:2)';
perp = t(3:4)';
hits = [
[m(1) + ((tr(2) - m(2))/perp(2))*perp(1) tr(2)]; % top border
[tr(1) m(2) + ((tr(1) - m(1))/perp(1))*perp(2)]; % right border
[C(1) C(2)]; ... % The calculated point
[m(1) + ((bl(2) - m(2))/perp(2))*perp(1) bl(2)]; % bottom border
[bl(1) m(2) + ((bl(1) - m(1))/perp(1))*perp(2)] % left border
];
vals = ((hits(:,1) + hits(:,2)) - (m(1) + m(2))); % Distance from the midpoint
vals = vals * vals(3); % make bounds in the opposite direction -ve
vals = vals .* (vals > 0) + (vals < 0) * 9e50; % make the -ve items enormous
[v index] = min(vals); % Find the minimum value
newC = hits(index,:);
% plot(hits(:,1),hits(:,2),'db');
% More helper lines
% for h=hits'
% h
% plot([h(1) m(1)],[h(2) m(2)],'k-o');
% axis([bl(1)-1000,tr(1)+1000,bl(2)-1000,tr(2)+1000]);
%
% end
if ~(newC(1) == C(1) && newC(2) == C(2))
newCs = [newCs;newC];
end
% Add to arrays for each cell, then only add to cell if its one of
% the outer two
end
if (length(newCs) < 3)
cells{d} = [cells{d};C];
cells{e} = [cells{e};C];
cells{f} = [cells{f};C];
plot(C(1),C(2),'sm');
else
dont_fake = [dont_fake d e f];
ls = [0 0 0];
% 1-2 length
ls(1) = (newCs(2,1) - newCs(1,1)).^2 + (newCs(2,2) - newCs(1,2)).^2;
% 2-3 length
ls(2) = (newCs(3,1) - newCs(2,1)).^2 + (newCs(3,2) - newCs(2,2)).^2;
% 3-1 length
ls(3) = (newCs(1,1) - newCs(3,1)).^2 + (newCs(1,2) - newCs(3,2)).^2;
[v index] = max(ls); % Find the largest distance and don't plot
if (index == 1)
cells{d} = [cells{d};newCs(1,:)];
cells{d} = [cells{d};newCs(2,:)];
cells{e} = [cells{e};newCs(1,:)];
cells{f} = [cells{f};newCs(2,:)];
% plot(newCs(1,1),newCs(1,2),'ro');
% plot(newCs(2,1),newCs(2,2),'ro');
% plot(newCs(3,1),newCs(3,2),'rx');
elseif (index == 2)
cells{f} = [cells{f};newCs(2,:)];
cells{f} = [cells{f};newCs(3,:)];
cells{d} = [cells{d};newCs(2,:)];
cells{e} = [cells{e};newCs(3,:)];
% plot(newCs(1,1),newCs(1,2),'gx');
% plot(newCs(2,1),newCs(2,2),'go');
% plot(newCs(3,1),newCs(3,2),'go');
elseif (index ==3)
cells{e} = [cells{e};newCs(1,:)];
cells{e} = [cells{e};newCs(3,:)];
cells{d} = [cells{d};newCs(1,:)];
cells{f} = [cells{f};newCs(3,:)];
% plot(newCs(1,1),newCs(1,2),'bo');
% plot(newCs(2,1),newCs(2,2),'bx');
% plot(newCs(3,1),newCs(3,2),'bo');
end
end
lines = [lines;struct('m',m12,'d',perp12,'points',[d,e],'C',C);struct('m',m13,'d',perp13,'points',[d,f],'C',C);struct('m',m23,'d',perp23,'points',[e,f],'C',C)];
% axis([bl(1)-50,tr(1)+50,bl(2)-50,tr(2)+50]);
end
% Finding Edge (fake) sites
lid = 1;
while lid <= length(lines)
a = (lines(lid).m'*ones([1 21]) + (mus' * lines(lid).d)')';
plot(a(:,1),a(:,2),'r:');
plot(lines(lid).m(1),lines(lid).m(2),'rx');
% Neg = 1, Pos = 2
ready = [0,0];
pid = 1;
for p=points'
if ~(lines(lid).points(1) == pid || lines(lid).points(2) == pid)
p = p';
v = dot(lines(lid).d,p - lines(lid).m);
if v > 0
ready(2) = ready(2) + 1;
elseif v < 0
ready(1) = ready(1) + 1;
end
end
pid = pid + 1;
end
ready(1) = ready(1) < 1;
ready(2) = ready(2) < 1;
if (ready(1) || ready(2))
%x
% To hit the left
intersector1 = (bl(1) - lines(lid).m(1))/lines(lid).d(1);
y1 = lines(lid).m(2) + intersector1 * lines(lid).d(2);
% To hit the right
intersector2 = (tr(1) - lines(lid).m(1))/lines(lid).d(1);
y2 = lines(lid).m(2) + intersector2 * lines(lid).d(2);
if (ready(1))
if (intersector1 <= 0 && y1 >= bl(2) && y1 <= tr(2) && lines(lid).C(1) >= bl(1))
newsite = [bl(1) y1];
plot(newsite(1),newsite(2),'sr');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
if (intersector2 <= 0 && y2 >= bl(2) && y2 <= tr(2) && lines(lid).C(1) <= tr(1))
newsite = [tr(1) y2];
plot(newsite(1),newsite(2),'sg');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
end
if (ready(2))
if (intersector1 >= 0 && y1 >= bl(2) && y1 <= tr(2) && lines(lid).C(1) >= bl(1))
newsite = [bl(1) y1];
plot(newsite(1),newsite(2),'sb');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
if (intersector2 >= 0 && y2 >= bl(2) && y2 <= tr(2) && lines(lid).C(1) <= tr(1))
newsite = [tr(1) y2];
plot(newsite(1),newsite(2),'sk');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
end
%y
% To hit the bottom
intersector1 = (bl(2) - lines(lid).m(2))/lines(lid).d(2);
x1 = lines(lid).m(1) + intersector1 * lines(lid).d(1);
% To hit the top
intersector2 = (tr(2) - lines(lid).m(2))/lines(lid).d(2);
x2 = lines(lid).m(1) + intersector2 * lines(lid).d(1);
if (ready(1))
if (intersector1 <= 0 && x1 >= bl(1) && x1 <= tr(1) && lines(lid).C(2) >= bl(2))
newsite = [x1 bl(2)];
plot(newsite(1),newsite(2),'dr');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
if (intersector2 <= 0 && x2 >= bl(1) && x2 <= tr(1) && lines(lid).C(2) <= tr(2))
newsite = [x2 tr(2)];
plot(newsite(1),newsite(2),'dg');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
end
if (ready(2))
if (intersector1 >= 0 && x1 >= bl(1) && x1 <= tr(1) && lines(lid).C(2) >= bl(2))
newsite = [x1 bl(2)];
plot(newsite(1),newsite(2),'db');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
if (intersector2 >= 0 && x2 >= bl(1) && x2 <= tr(1) && lines(lid).C(2) <= tr(2))
newsite = [x2 tr(2)];
plot(newsite(1),newsite(2),'dk');
cells{lines(lid).points(1)} = [cells{lines(lid).points(1)};newsite];
cells{lines(lid).points(2)} = [cells{lines(lid).points(2)};newsite];
end
end
end
lid = lid + 1;
end
for corner = [bl' tr' [bl(1);tr(2)] [tr(1);bl(2)]]
% Find nearest point, add the corner as a (fake) site to the point
closest = [];
r2 = inf;
p = 1;
while p <= length(points)
% plot(points(p,1),points(p,2),'ko');
new_r2 = (points(p,1) - corner(1))^2 + (points(p,2) - corner(2))^2;
if (new_r2 < r2)
closest = [p];
r2 = new_r2;
elseif (new_r2 == r2)
closest = [closest p];
end
p = p + 1;
end
for p=closest
cells{p} = [cells{p};corner'];
end
end
axis([bl(1)-50,tr(1)+50,bl(2)-50,tr(2)+50]);
animate = 'var counter = 0;function startup() {animate()}; function animate() {var is;var mult;var cells = document.getElementsByTagName(''polygon'');for (cell in cells) {if (cell >= 0) {is = cells[cell].getAttribute(''rel'');mult = Math.cos((1 - is) * counter * 0.05);cells[cell].setAttribute(''fill-opacity'',1- mult*mult*(1 - is));}}counter++;setTimeout(''animate()'',60)}';
fp = fopen('output.svg','w');
fwrite(fp,sprintf('<?xml version="1.0" standalone="no"?>\n<svg onload="startup()" viewBox = "%.0f %.0f %.0f %.0f" version = "1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">\n<script>%s</script>\n',bl(1),bl(2),tr(1),tr(2),animate));
i = 1;
while i <= length(cells)
% Sort sites
len=length(cells{i}(:,1));
av = [sum(cells{i}(:,1)) sum(cells{i}(:,2))]/len;
angle = atan2(cells{i}(:,2)-av(2),cells{i}(:,1)-av(1));
[angle_sorted,perm]=sort(angle);
p_sorted = cells{i};
p_sorted(:,1) = cells{i}(perm,1);
p_sorted(:,2) = cells{i}(perm,2);
%patch(p_sorted(:,1),p_sorted(:,2),i);
p_str = '';
for p=p_sorted'
p_str = sprintf('%s %.0f,%.0f',p_str,p(1),tr(2) - p(2) + bl(2));
end
fwrite(fp,sprintf(' <polygon id="cell-%.0f" points = "%s" rel="%.2f" fill-opacity="%.2f" style="fill:rgb(%.0f,%.0f,%.0f);" stroke = "black" stroke-width = "1"/>\n',i,p_str,1 - point_colours(i,1),1 - point_colours(i,1),point_colours(i,2)*255,point_colours(i,3)*255,point_colours(i,4)*255));
i = i + 1;
end
fwrite(fp,'</svg>');
fclose(fp);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment