Shadowart
Status: stable | |
|---|---|
| Beschreibung | Silhouetten Schablonen für Schattenbild |
| Autor: | spezi ptflea |
| Version | 1 |
| PayPal | |
Beschreibung
Als Idee für die Bamberger Lichthöfe 2013 haben wir, inspiriert durch die Arbeiten von Kumo Yamashita, versucht diverse schattenwerfende Objekte zu basteln. Dabei wurde eine Silhouette entsprechend der Lichtquelle angepasst, damit im Schatten das Ausgangssbild rekonstruiert wird. Die verzerrten Konturen wurden für die Lichthöfe aus Sperrholz mit der CNC Fräse ausgefräst.
Erstellung des Schattenobjekts
Erste Versuche
Wir haben uns der Verzerrung mit dem Versuch- und Irrtumprinzip genähert.
Zuerst mussten wir feststellen, dass eine Stauchung nicht ausreicht, sondern zusätzlich eine Trapezverzerrung notwendig ist. Weiterhin ist die Stauchung nicht linear, sondern wird nach oben stärker.
Mit Gimp (gimp-tool-perspective) lässt sich eine Trapezverzerrung mit nichtlinearer Stauchung erstellen. Danach wurde das Bild mit Inkscape (tutorial-tracing) in eine Vektorgrafik umgewandelt und noch stärker gestaucht.
Die fertigen Vektordaten wurden dann mit der CNC Fräse aus 4mm Pappelsperrholz ausgefräst.
Verbesserung der Genauigkeit
Das beschriebene Vorgehen hat zwar zu brauchbaren Ergebnissen geführt, ist jedoch umständlich zu handhaben und es ist nicht möglich genau zu arbeiten. Z.B. den Abstand der Lichtquelle genau einzustellen.
ptflea hat sich der Sache angenommen und ein Programm in Processing geschrieben das SVG-Dateien aus Inkscape entgegen nimmt und die Verzerrung in Abhängigkeit der Entfernung der Lichtquelle berechnet.
Das Programm zerlegt das Zielobjekt in einzelne Punkte und verbindet jeden einzelnen Punkt im dreidimensionalen Raum mit der Lichtquelle. Die Schnittpunkte mit der Zielebene ( entspricht dem verzerrten Objekt) werden miteinander verbunden und als SVG-Datei gespeichert.
Programm in Processing
Die Ausgangsdaten werden in Inkscape in der gewünschten 'Schattenwurfgrösse' in mm erstellt und als SVG-Datei im Ordner 'data' im Sketchordner abgelegt.
Wichtig: Das Objekt muss auf dem Kopf stehen und links oben angelegt sein (siehe Beispiele)
Die Ausgabedatei liegt im Sketchordner mit der Namensergänzung '_Shadow'.
Die fertige Datei muss noch in Inkscape bearbeitet werden, da die Linien nicht geschlossen sind:
- Objekt markieren und mit 'F2' in den Knotenbearbeitungsmodus wechseln.
- Mit 'STRG'+'A' alles markieren und in der Symbolleiste 'Gewählte Endknoten durch ein neues Segment verbinden' klicken.
Die Verarbeitung der SVG-Dateien übernimmt die Geomerative-Libary
Hier ist der Code:
import geomerative.*; // http://www.ricardmarxer.com/geomerative/
//SVG muss auf dem Kopf stehen und links oben angelegt sein, in mm der gewünschten Grösse
//Input-SVG muss im Data-Ordner liegen
//Output-SVG liegt im Sketch-Ordner -> Originalname+_'Shadow'
String Dateiname = "space_invader";
//Koordinaten der Lichtquelle in mm
int lichtY = 150; //Entfernung
int lichtZ = 80; //Höhe
// x wird immer in die Mitte des Objektes gelegt -> siehe Variable 'mitte'
float mm2px_faktor = 3.54331; // Umrechung der px von Inkscape zu mm 1mm entspricht 3.54331px
int trennwert = 10; //Inseln erkennen und nicht verbinden -> kleine Zahl wenn Insel sehr nahe beieinander
float scale = 1 ; //Grösse der Darstellung auf dem Bildschirm
String[] SVGdatei = new String[2]; //String Array zur koordinatenausgabe
RPoint[] points; //Array für Koordinaten
int i = 0; // Zähler
float mitte = 0; // Mitte der X-Achse des Objektes
float PXlast = 0; // vorheriger x-Punkt um Linie zeichnen zu können
float PYlast = 0; // vorheriger y-Punkt um Linie zeichnen zu können
//3D-Raum mit Schnittebene
PVector[] face = new PVector[3];
PVector n;//normal
Ray r1;
void setup(){
size(1000,600,P3D); // Grösse der Anzeigefläche
//Fläche der 'Verzerrungsebene' definieren
face[0] = new PVector(-100,0,0);
face[1] = new PVector(0,0,100);
face[2] = new PVector(100,0,0);
r1 = new Ray(new PVector(-100,160,50),new PVector(0,200,300));
// Load given svg file in sketch/data
RG.init(this);
RShape objShape = RG.loadShape(Dateiname + ".svg");
// Linien in Punkte zerlegen
points = objShape.getPoints();
background(255);
translate(width/2, height/2,-500);
rotateX(map(90,0,height,-PI,PI));
rotateY(map(90,0,width,-PI,PI));
PVector c = new PVector();//centroid
for(PVector p : face) c.add(p);
c.div(3.0);
PVector cb = PVector.sub(face[2],face[1]);
PVector ab = PVector.sub(face[0],face[1]);
n = cb.cross(ab);//compute normal
//Mittelpunkt der x-Achse des Objektes festelegen -> X der Lichtquelle
for (int x=0; x<points.length-1; x++) {
if (points[x].x > mitte) {
mitte = points[x].x;
}
}
mitte = mitte/2;
noLoop(); // damit draw() nur ein mal ausgeführt wird
//SVG Datei Basics
SVGdatei[0] = "<svg\n version=\"1.1\"\n width=\"210mm\"\n height=\"297mm\"\n id=\"svg2\">\n <g id=\"layer1\">\n <path\n d=\"";
SVGdatei[1] = " id=\"path5917\"\n style=\"fill:none;stroke:#000000;stroke-width:0.1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none\" />\n </g>\n</svg>";
}
void draw(){
//umrechung der Lichtposition von mm zu px, da Inkspace im SVG px verwendet
lichtY = (int)(-lichtY * mm2px_faktor); //Entfernung
lichtZ = (int)(lichtZ * mm2px_faktor);
// Solange noch Punkte im Array sind Linie von Punkt zu Punkt zeichnen
for (int i=0; i < points.length - 1; i++) {
//Vector von der Lichtposition zu einem Punkt des Objektes anlegen
r1 = new Ray(new PVector(mitte,lichtY,lichtZ),new PVector(points[i].x, points[i].y,0));
//http://paulbourke.net/geometry/planeline/
//line to plane intersection u = N dot ( P3 - P1 ) / N dot (P2 - P1), P = P1 + u (P2-P1), where P1,P2 are on the line and P3 is a point on the plane
//Schnittpunkt mit der 'Verzerrungsebene' berechnen
PVector P2SubP1 = PVector.sub(r1.end,r1.start);
PVector P3SubP1 = PVector.sub(face[0],r1.start);
float u = n.dot(P3SubP1) / n.dot(P2SubP1);
PVector P = PVector.add(r1.start,PVector.mult(P2SubP1,u));
strokeWeight(1);
n.normalize();
//original zeichnen
point(points[i].x, points[i].y);
// Inseln erkennen und nicht verbinden
if ((PXlast < 0.001) || (abs(PXlast-P.x) > trennwert) || (abs(P.x-PXlast) > trennwert) || (abs(PYlast-P.z) > trennwert) || (abs(P.z-PYlast) > trennwert)) {
PXlast = P.x;
PYlast = P.z;
SVGdatei[0] = SVGdatei[0] + "M "; // Trennung der Linie einfügen
}
//verzerrtes Objekt zeichnen
line(PXlast*scale,PYlast*scale,0,P.x*scale,P.z*scale,0);
//Punkte in array speichern zur Ausgabe
SVGdatei[0] = SVGdatei[0] + PXlast + "," + PYlast + " ";
//letzten Punkt merken um die Punkte zu verbinden
PXlast = P.x;
PYlast = P.z;
}
//Punkte in die SVG-Datein schreiben
SVGdatei[0] = SVGdatei[0] + "\""; // Anführungsstriche einfügen
saveStrings(Dateiname + "_Shadow.svg", SVGdatei);
}
class Ray{
PVector start = new PVector(),end = new PVector();
Ray(PVector s,PVector e){ start = s ; end = e; }
}Beispiel: Space Invader
Ziel ist ein ca. 42 x 30 cm großer Space Invader der mit einer LED beleucht wird die 15 cm entfernt ist und einen Abstand von 8 cm zur Wand hat.



Beispiel: ccc-r0ket


Installation
Die gefrästen Objekte wurden mit Kreppband an der Wand befestigt.
Als Lichtquelle diente jeweils eine PowerLED mit Abstrahlwinkel: 120°
Bilder
-
Ampelmännchen
-
Hände
-
sitzendes Mädchen
-
Blick in den Gang
-
Kind
-
Cadillac

