Shadowart

Shadowart

Status: stable

Beschreibung Silhouetten Schablonen für Schattenbild
Autor: spezi ptflea
Version 1
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:

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.

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