Shadowart

Crystal Clear action run.png
Shadowart

Status: stable

Shadowart space invader kl.jpg
Beschreibung Silhouetten Schablonen für Schattenbild
Autor: spezi ptflea
Version 1
Flattr Flattr Shadowart
PayPal Spenden für Shadowart

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: <syntaxhighlight lang="c" line enclose="div"> 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;  }

} </syntaxhighlight>

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.

Space Inavder (SVG)
Space Inavder Installation
Space Inavder verzerrt (SVG) -> runterladen und laserschneiden!!!

Beispiel: ccc-r0ket

ccc roket (SVG)
ccc roket verzerrt (SVG) -> runterladen und laserschneiden!!!

Installation

Die gefrästen Objekte wurden mit Kreppband an der Wand befestigt.

Als Lichtquelle diente jeweils eine PowerLED mit Abstrahlwinkel: 120°

Bilder