├── settings.txt ├── data └── desert_depth_4_3.png ├── license.txt ├── Pointcloud_Render.pde ├── Settings.pde ├── Controls.pde ├── Data.pde ├── Cam.pde ├── PointExport.pde └── Encoding.pde /settings.txt: -------------------------------------------------------------------------------- 1 | Output Filename 2 | output 3 | 4 | Output Format (OBJ, PLY, PNG, ASC, BIN) 5 | ply -------------------------------------------------------------------------------- /data/desert_depth_4_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/n1ckfg/Pointcloud_Render/HEAD/data/desert_depth_4_3.png -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Pointcloud_Render was developed with support from: 2 | Canada Council on the Arts 3 | Eyebeam Art + Technology Center 4 | 5 | Copyright (c) 2017 Nick Fox-Gieg 6 | http://fox-gieg.com 7 | 8 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | 10 | The MIT License 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in 20 | all copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 28 | THE SOFTWARE. -------------------------------------------------------------------------------- /Pointcloud_Render.pde: -------------------------------------------------------------------------------- 1 | int sW = 640; 2 | int sH = 360; 3 | ArrayList imgNames; 4 | int counter = 0; 5 | String filePath = "render"; 6 | String fileType = "obj"; //obj, ply, png 7 | float zscale = 3; //orig 3, 1 looks better in 2D image but 3 looks better for OBJ, PLY 8 | float zskew = 10; 9 | //************************************** 10 | boolean firstRun = true; 11 | 12 | Cam cam; 13 | float[][] gray; 14 | 15 | String[] numFiles; 16 | 17 | PImage img, buffer; 18 | 19 | void setup() { 20 | size(50, 50, P3D); 21 | Settings settings = new Settings("settings.txt"); 22 | if (fileType.toLowerCase().equals("png")) { 23 | zscale = 1; //looks better if saving frames 24 | } else { 25 | zscale = 3; 26 | } 27 | chooseFolderDialog(); 28 | while (firstRun) { 29 | try { 30 | if (imgNames.size() > 0) img = loadImage((String) imgNames.get(counter)); 31 | } catch(Exception e) { } 32 | } 33 | sW = img.width; 34 | sH = img.height; 35 | surface.setSize(sW, sH); 36 | cam = new Cam(); 37 | gray = new float[sH][sW]; 38 | stroke(255); 39 | } 40 | 41 | void draw() { 42 | background(0); 43 | img = loadImage((String) imgNames.get(counter)); 44 | exportMain(); 45 | //~~~ 46 | //pushMatrix(); 47 | //translate(-sW / 2, -sH / 2); 48 | int step = 2; 49 | for (int y = step; y < sH; y += step) { 50 | float planephase = 0.5 - (y - (sH / 2)) / zskew; 51 | for (int x = step; x < sW; x += step){ 52 | stroke(gray[y][x]); 53 | //point(x, y, (gray[y][x] - planephase) * zscale); 54 | line(x, y, (gray[y][x] - planephase) * zscale, x+1, y, (gray[y][x] - planephase) * zscale); 55 | } 56 | } 57 | //popMatrix(); 58 | 59 | cam.run(); 60 | //~~~ 61 | if (fileType.toLowerCase().equals("png")) saveFrame(filePath + "/" + fileName + zeroPadding(counter+1,imgNames.size()) + "." + fileType); 62 | if (counter < imgNames.size()) counter++; 63 | if (counter == imgNames.size()) { 64 | openAppFolderHandler(); 65 | exit(); 66 | } 67 | } 68 | 69 | static final int getGray(color value) { 70 | return max((value >> 16) & 0xff, (value >> 8 ) & 0xff, value & 0xff); 71 | } 72 | 73 | String zeroPadding(int _val, int _maxVal){ 74 | String q = ""+_maxVal; 75 | return nf(_val,q.length()); 76 | } 77 | 78 | //~~~ END ~~~ -------------------------------------------------------------------------------- /Settings.pde: -------------------------------------------------------------------------------- 1 | class Settings { 2 | 3 | String[] data; 4 | 5 | Settings(String _s) { 6 | try { 7 | data = loadStrings(_s); 8 | for (int i=0;i0)) { 83 | prefix=templ.substring(0, first); 84 | suffix=templ.substring(last+1); 85 | 86 | // Comment out if you want to use absolute paths 87 | // or if you're not using this inside PApplet 88 | //if(sketchPath!=null) prefix=savePath(prefix); 89 | 90 | index=0; 91 | ok=false; 92 | 93 | do { 94 | padstr=""; 95 | numstr=""+index; 96 | for(int i=0; i< count-numstr.length(); i++) padstr+="0"; 97 | s=prefix+padstr+numstr+suffix; 98 | 99 | f=new File(s); 100 | ok=!f.exists(); 101 | index++; 102 | 103 | // Provide a panic button. If index > 10000 chances are it's an 104 | // invalid filename. 105 | if(index>10000) ok=true; 106 | 107 | } 108 | while(!ok); 109 | 110 | // Panic button - comment out if you know what you're doing 111 | if(index> 10000) { 112 | println("getIncrementalFilename thinks there is a problem - "+ 113 | "Is there more than 10000 files already in the sequence "+ 114 | " or is the filename invalid?"); 115 | println("Returning "+prefix+"ERR"+suffix); 116 | return prefix+"ERR"+suffix; 117 | } 118 | } 119 | 120 | return s; 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /Cam.pde: -------------------------------------------------------------------------------- 1 | // https://processing.org/reference/camera_.html 2 | class Cam { 3 | 4 | PVector pos = new PVector(0,0,0); 5 | PVector poi = new PVector(0,0,0); 6 | PVector up = new PVector(0,0,0); 7 | 8 | PVector mouse = new PVector(0,0,0); 9 | PGraphics3D p3d; 10 | PMatrix3D proj, cam, modvw, modvwInv, screen2Model; 11 | 12 | String displayText = ""; 13 | PFont font; 14 | int fontSize = 12; 15 | 16 | void init() { 17 | p3d = (PGraphics3D) g; 18 | //proj = new PMatrix3D(); 19 | cam = new PMatrix3D(); 20 | //modvw = new PMatrix3D(); 21 | modvwInv = new PMatrix3D(); 22 | screen2Model = new PMatrix3D(); 23 | 24 | font = createFont("Arial", fontSize); 25 | } 26 | 27 | PVector screenToWorldCoords(PVector p) { 28 | //proj = p3d.projection.get(); 29 | cam = p3d.modelview.get(); //camera.get(); 30 | //modvw = p3d.modelview.get(); 31 | modvwInv = p3d.modelviewInv.get(); 32 | screen2Model = modvwInv; 33 | screen2Model.apply(cam); 34 | float screen[] = { p.x, p.y, p.z }; 35 | float model[] = { 0, 0, 0 }; 36 | model = screen2Model.mult(screen, model); 37 | 38 | return new PVector(model[0] + (poi.x - width/2), model[1] + (poi.y - height/2), model[2]); 39 | } 40 | 41 | void screenToWorldMouse() { 42 | mouse = screenToWorldCoords(new PVector(mouseX, mouseY, poi.z)); 43 | } 44 | 45 | Cam() { 46 | defaultPos(); 47 | defaultPoi(); 48 | defaultUp(); 49 | init(); 50 | } 51 | 52 | Cam(PVector _pos) { 53 | pos = _pos; 54 | defaultPoi(); 55 | defaultUp(); 56 | init(); 57 | } 58 | 59 | Cam(PVector _pos, PVector _poi) { 60 | pos = _pos; 61 | poi = _poi; 62 | defaultUp(); 63 | init(); 64 | } 65 | 66 | Cam(PVector _pos, PVector _poi, PVector _up) { 67 | pos = _pos; 68 | poi = _poi; 69 | up = _up; 70 | init(); 71 | } 72 | 73 | void update() { 74 | screenToWorldMouse(); 75 | } 76 | 77 | void draw() { 78 | camera(pos.x, pos.y, pos.z, poi.x, poi.y, poi.z, up.x, up.y, up.z); 79 | drawText(); 80 | } 81 | 82 | void run() { 83 | update(); 84 | draw(); 85 | } 86 | 87 | void move(float x, float y, float z) { 88 | PVector p = new PVector(x,y,z); 89 | pos = pos.add(p); 90 | poi = poi.add(p); 91 | } 92 | 93 | void defaultPos() { 94 | pos.x = width/2.0; 95 | pos.y = height/2.0; 96 | pos.z = (height/2.0) / tan(PI*30.0 / 180.0); 97 | } 98 | 99 | void defaultPoi() { 100 | poi.x = width/2.0; 101 | poi.y = height/2.0; 102 | poi.z = 0; 103 | } 104 | 105 | void defaultUp() { 106 | up.x = 0; 107 | up.y = 1; 108 | up.z = 0; 109 | } 110 | 111 | void reset() { 112 | defaultPos(); 113 | defaultPoi(); 114 | defaultUp(); 115 | } 116 | 117 | void drawText() { 118 | if (!displayText.equals("")) { 119 | pushMatrix(); 120 | translate((pos.x - (width/2)) + (fontSize/2), (pos.y - (height/2)) + fontSize, poi.z); 121 | textFont(font, fontSize); 122 | text(displayText, 0, 0); 123 | popMatrix(); 124 | } 125 | } 126 | 127 | } 128 | 129 | // TODO 130 | // https://processing.org/reference/frustum_.html 131 | // https://processing.org/reference/beginCamera_.html 132 | // https://processing.org/reference/endCamera_.html -------------------------------------------------------------------------------- /PointExport.pde: -------------------------------------------------------------------------------- 1 | Data dataVert, dataMain; 2 | ArrayList lidarPoints; 3 | int vertCounter = 0; 4 | String url = ""; 5 | 6 | void exportMain() { 7 | url = filePath + "/" + fileName + zeroPadding(counter+1,imgNames.size()) + "." + fileType; 8 | vertCounter = 0; 9 | 10 | dataMain = new Data(); 11 | dataMain.beginSave(); 12 | dataVert = new Data(); 13 | dataVert.beginSave(); 14 | lidarPoints = new ArrayList(); 15 | 16 | buffer = img; 17 | for (int y = 0; y < sH; y++) { 18 | for (int x = 0; x < sW; x++) { 19 | // FIXME: this loses Z-resolution about tenfold ... 20 | // -> should grab the real distance instead... 21 | color argb = buffer.pixels[y*width+x]; 22 | gray[y][x] = getGray(argb); 23 | if (gray[y][x] > 0) { 24 | vertCounter++; 25 | if (fileType.toLowerCase().equals("obj")) { 26 | objVert(x,y); 27 | } else if (fileType.toLowerCase().equals("ply")) { 28 | plyVert(x,y); 29 | } else if (fileType.toLowerCase().equals("asc")) { 30 | ascVert(x,y); 31 | } else if (fileType.toLowerCase().equals("bin")) { 32 | binVert(x,y); 33 | } 34 | } 35 | } 36 | } 37 | if (fileType.toLowerCase().equals("obj")) { 38 | objHeader(); 39 | compileVert(); 40 | objFooter(); 41 | writeAscii(); 42 | } else if (fileType.toLowerCase().equals("ply")) { 43 | plyHeader(); 44 | compileVert(); // ply doesn't need a footer 45 | writeAscii(); 46 | } else if (fileType.toLowerCase().equals("asc")) { 47 | compileVert(); // asc doesn't need header or footer 48 | writeAscii(); 49 | } else if (fileType.toLowerCase().equals("bin")) { 50 | writeBin(); 51 | } 52 | } 53 | 54 | void writeAscii() { 55 | dataMain.endSave(url); 56 | } 57 | 58 | void writeBin() { 59 | float[] floats = floatListToArray(lidarPoints); 60 | floatsToBytes(floats, url); 61 | } 62 | 63 | float[] floatListToArray(ArrayList list) { 64 | float[] floats = new float[list.size()]; 65 | for (int i=0; i> (n - j)) & 1); 23 | } 24 | return bytes; 25 | } 26 | 27 | String[] decodeNBits(byte[] b, int n) { 28 | ArrayList stringsL= new ArrayList(); 29 | for (int i=0; i> 8) & 0xFF); 122 | bytes[(i*4)+2] = byte((fi >> 16) & 0xFF); 123 | bytes[(i*4)+3] = byte((fi >> 24) & 0xFF); 124 | } 125 | 126 | saveBytes(_fileName, bytes); 127 | } --------------------------------------------------------------------------------