Get the RGB value of a pixel on a textured sphere ?

edited April 2018 in Questions about Code

I have a P3D sketch with a Sphere primitive at 0,0,0. Surrounding the sphere (though not touching) are a number of cubes that are orientated so their z-axis points to the center of the sphere.

The sphere is textured with an image file. How would I go about finding the colour on the surface of the sphere at the point a line from each cube to 0,0,0 intersects the surface ?

looking for general ideas at this stage... was thinking collision detection,raytracing... but also wondered (hoping!) if there may be a simpler method ?

cheers, mala

Tagged:

Answers

  • It would help to see your entire code

    On general I would recommend screenX and screenY and then use get(mouseX,mouseY) and compare to a color of a cube you stored

    Anyway when this poses problems (eg when using lights()) you can also use an invisible PGraphics

  • edited April 2018

    Hi Chris,

    I think you've got the wrong end of stick (vector) ;)

    I'm trying to get the colour (RGB) of the texture at specfic point on the surface of the sphere. This point is defined by the intersection of a line between the center of any of the cubes and the sphere's surface. (quick sketch in a 3d program attached)

    I use the method you mention to pick the actual cubes when I want to select those for another function.

    Hope that makes it a bit clearer ? Cheers, malasphereTex

  • edited April 2018

    One approach is to not use the sphere primitive -- instead, implement your own sphere texture using e.g. textureSphere example -- now you can know what any given point on the sphere is -- you could for example cache those values in an array while rendering, or do a lookup.

    See also this very old thread discussing Toxi annd various iterations of "generic code for drawing a textured sphere"

  • Thanks Jeremy,

    I have come across that example when trying to figure out my texture transparency question here:

    https://forum.processing.org/two/discussion/27787/set-transparency-of-texture-image-on-sphere-primitive-p3d

    my most recent comment there in large part explains what I'm trying to do here ( not sure if I should cut/paste it here too?)

    I need to look at the texturesphere code and try to understand it properly. One thing that is not immediately obvious to me, is the UV mapping (projection) method used in that example, presuambly the world32k image uses Equirectangular projection.

    Cheers, mala

  • If you are curious about how setTexture() works in Processing P3D (openGL), here it is:

    Based on these two related threads, one possible approach is that you could use the cubes as an interface to sampling a 2D PImage. Position the cube with spherical coordinates, then transform those coordinates to a lookup on the original 2D PImage.

    Downsides of this approach -- it could get tricky around the poles -- you need to be sampling / projecting in exactly the same way that the sphere renderer did.

  • Exactly. And because the source image in OPs example is not itself an equirectangular projection -- it looks like a photo of a tree, un-projected -- its top and bottom rows won't already be a single pixel value, and so an off-by-1 error in sampling the source image by indicating areas near the top and bottom of the sphere could (potentially) give a dramatically different color result than what displayed on the sphere using setTexture().

  • Thanks guys. The photo on the example sketch was a bad choice, seems to be the only I have that is not mapped properly! here's the enviro hdr exported to jpeg : EnviroExample

    They should all be 2:1 ratio, equirectangular and the enviro textures are usually quite small pixel dimensions, which actually helps me. I'm not trying to map 1 for 1 a pixel to a light.There are only 150 or so lights, hence mentioning averaging the area of the texture as well.

    @Jeremy In terms of your suggestion, The cubes are already distrubuted a final fixed position and rotation, there is a cube/light at exactly the top pole but not one at the bottom. Your suggestion to transform the cubes position from spherical coords to lookup on the original image is interesting but what happens when the sphere/image is rotated ?... I'm guessing that could be accounted for ? but I'm already lost before I've started.

  • can't you just say colGet=get(mouseX,mouseY);

    then you just treat what's visible as 2D and get the color

    additionally you can use if(dist(mouseX,mouseY, circleX, circleY)<diameter) to use get only when mouse is really on the spere

  • It needs to be automated really, rather than making the user manually pick with the mouse for every sample required of the map.

  • edited April 2018

    re:

    there is a cube/light at exactly the top pole but not one at the bottom

    I take it you mean ambient 3D lighting. Do you want the cube value to be the pixel value of the texture image pre-lighting, or post-lighting?

    This has really gotten far into territory where an MCVE would help us help you better.

  • edited April 2018

    @ Jeremy Thanks for your patience.

    There are no lights in my P3D sketch, so kinda pre-lighting ;)

    This example has camera with nav, all cubes in their positions and a primitive sphere with a texture, the sphere can be rotated and the cubes can be selected, though nothing will happen with the cubes other than a hightlight. Hover over a cube and you get it's ID scribbled in top left corner.

    Code is stripped down but still quite a bit : // // LMB + drag to move around globe // LMB double click to reset view // RMB click over a node to select ( in this example won't do anything other than highlight it for now // RMB double click to deselect all nodes // LEFT/RIGHT Arrow keys to rotate globe around it's Y axis // UP/DOWN Arrow keys to rotate globe around it's X axis //

        PGraphicsOpenGL pg;
        MyCamera cam;
        //
        char nNodes = 156;       //cubes/light proxies
        Node nodes[] = new Node[nNodes];
        Node picked = null;
    
        //Preset cube colors (outline)
        final color defaultNodeStroke = color(240, 240, 240);
        final color overNodeStroke = color(0, 255, 0);
        final color activeNodeStroke = color(255, 0, 0);
        color foundColorStroke;
        //
        PShape globe; //image mapper sphere base
        float globeRadius = 110;
        float globeRotX = 0;
        PImage img;   
        String currentNode;
    
        void settings() {
          size(1000, 1000, P3D);
        }
    
    
        void setup() {
    
    
          //Build & textureGlobe
          img = loadImage("http://" + "www.jbcse.com/images/reference/world32k.jpg");  
          globe = createShape(SPHERE, globeRadius);
          globe.setStroke(false);
          globe.setTexture(img); 
    
    
          cam = new MyCamera(400);
          camera(0, 0, 400, 0, 0, 0, 0, 1, 0); //cam start up values
    
          //Create Nodes(cubes)
          for (int i = 0; i < nodes.length; i++) //
          {          //needs some adjustments - basically mapping real 3D world to processing world
            nodes[i] = new Node("" + i, 0xff000000 + i, new PVector(map(nodeVerts[i][0], 0, 255, -128, 128), map(nodeVerts[i][1], 0, 255, 128, -128), map(nodeVerts[i][2], 0, 255, 128, -128)));
          }
          pg = (PGraphicsOpenGL)createGraphics(width, height, P3D);
        }
    
    
        void draw() {
    
          noLights();
          background(60); 
          pushMatrix(); 
          shape(globe, 0, 0); //globe draw
          for (int i = 0; i < nodes.length; i++)  nodes[i].render();  // Cube render
    
          //cube picker
          picked = pickCube(mouseX, mouseY);
          if (picked != null) 
          { 
            picked.Over = true;
            currentNode = picked.name; // quick bodge
          } else {
            for (int i = 0; i < nodes.length; i++) {
              if (nodes[i].Active == false) {
                nodes[i].Over = false;
              }
            }
          }
    
          camera();
          //
          hint(DISABLE_DEPTH_TEST); 
          fill(255);
          textSize(30);  
          text("NODE:"+currentNode, 20, 40);      //display ID of node hovered over 
          hint(ENABLE_DEPTH_TEST); 
          //
          popMatrix();
        }
    
    
        //--------------------------------------------------------->>   Mouse & Key Input
        void mouseClicked(MouseEvent event) { 
          if (mouseButton == LEFT) {
            if (event.getCount() == 2) {  // double clicked left btn resets cam position 
              cam.reset();
            }
          } else if (mouseButton == RIGHT && event.getCount() == 2) { // double click right button deselects all nodes
            deselectAllNodes();
          } else if (mouseButton == RIGHT && picked != null) {  
            if (picked.Over == true) { 
              picked.Active = !picked.Active; // right click on a node to select
            }
          }
        }
    
        void mouseDragged() {
          cam.mouseDragged();
        }
    
    
        void mouseWheel(MouseEvent event) {  
          int e = event.getCount();
          cam.mouseWheel(e); // zoom cam in/ out
        }
    
    
        void keyPressed() { 
          if (key == CODED) {
    
            switch(keyCode) {
    
            case LEFT :    //spin globe left on y axis
              globe.rotateY(radians(-1)); 
              globeRotY --;
              break;
    
            case RIGHT:    //spin globe right on y axis
              globe.rotateY(radians(1)); 
              globeRotY ++;
              break;
    
            case UP:    //spin globe up on x axis 
              globe.rotateX(radians(1));
              break;
    
            case DOWN:    //spin globe down on x axis 
              globe.rotateX(radians(-1));
              break;
            }
          }
        }
    
    
        //------------------------------------------------>> Cam Class, NodeCubes & Picker
    
    
        //----------------------------------------->>>CAMERA CLASS<<<-------------------------------
        class MyCamera {
          PVector camPos, camLookAt, camUp; 
          float theta, phi;
          float camRadius, last_camRadius; 
          float mouseNowX, mouseNowY, last_mouseNowX, last_mouseNowY ; 
          boolean camReset = false; 
    
          MyCamera(float t_camRadius) {
            camRadius = t_camRadius;
            camPos = new PVector(0, 0, camRadius); 
            camLookAt = new PVector(0, 0, 0);
            camUp = new PVector(0, 1, 0);
          }
    
    
          public void mouseWheel(float zoom) {
            camRadius = constrain(cam.camRadius+zoom, 50, 400);
            camRun(); 
            last_camRadius = camRadius;
          }
    
          public void mouseDragged() {
            if (mouseButton == LEFT) {
              mouseNowX += mouseX-pmouseX;
              mouseNowY += mouseY-pmouseY;
              camRun();
            }
          }
    
          public void camRun() {
            //calc angles
            theta = map(mouseNowX, 0, width, 0, 360);
            phi = map(mouseNowY, 0, height, 60, 220);  
            //calc position from angles 
            camPos.x = (1.0* camRadius) * cos (radians(theta)) +camLookAt.x; 
            camPos.z = (1.0* camRadius) * sin (radians(theta)) +camLookAt.z;  
            camPos.y = ((1.0* camRadius) * cos (radians(phi)) +camLookAt.y)-20;
            //apply vectors to camera
            camera(camPos.x, camPos.y, camPos.z, 
              camLookAt.x, camLookAt.y, camLookAt.z, 
              camUp.x, camUp.y, camUp.z);
          }
    
          public void reset() { 
            camera(0, 0, 400, 0, 0, 0, 0, 1, 0); 
            camRadius = 400;
            camReset = true;
          }
        } 
    
        //--------------------------------------------->> The Node (cube) Class <<------------------
    
        class Node
        {
          String name;
          PShape Node, Node_1pg, nodeShape;
          int pickCol, strokeCol;
          PVector pos, rtp;  
          boolean Over = false; // is mouseOver ?
          boolean Active = false;  //  has it been clicked ?
    
    
          Node(String _name, int _pickCol, PVector _pos) 
          {
            this.name = _name; 
            this.pos = _pos;   
            this.Node = node();   
            this.Node_1pg = node();
            this.pickCol = _pickCol;  // **don't mess with this - is for unseen picker only**
          }
    
    
    
          public void render()   // for the one that is actually displayed
          {    
            Node.setFill(0);
            Node.setStrokeWeight(1.5);
            if (Active) {
              Node.setStroke(activeNodeStroke); // red for selected
            } else if (Over) {
              Node.setStroke(overNodeStroke);  // green for hovered over
            } else {
              Node.setStroke(foundColorStroke); // from globe
            }
    
            pushMatrix();
    
            float phi = acos(this.pos.y / sqrt(this.pos.x*this.pos.x + this.pos.y*this.pos.y + this.pos.z*this.pos.z)); //phi
            float theta = atan2(-this.pos.z, this.pos.x); //theta
    
            translate(this.pos.x, this.pos.y, this.pos.z);  
            rotateY(theta+PI/2);  //theta
            rotateX(phi+PI/2);  //phi
    
            shape(Node);
            popMatrix();
    
            // Find color from globe and use to color cube
            float u = 1.05 + (theta-radians(globeRotY))/TWO_PI;
            float v = 1.0 - phi/PI;
            img.loadPixels();
            foundColorStroke = img.pixels[int(v*img.height)*img.width+int(u*img.width)];
          }
    
    
    
          public void displayForPicker()   //not actually seen just there for the picker in the background
          {   
            Node_1pg.setFill(color(pickCol));
            Node_1pg.setStroke(false);  
            pg.pushMatrix();
            float angleX = acos(this.pos.y / sqrt(this.pos.x*this.pos.x + this.pos.y*this.pos.y + this.pos.z*this.pos.z)); 
            float angleY = atan2(-this.pos.z, this.pos.x);
            pg.translate(this.pos.x, this.pos.y, this.pos.z);  
            pg.rotateY(angleY+PI/2);  
            pg.rotateX(angleX+PI/2);
            pg.shape(Node_1pg); 
            pg.popMatrix();
          }
    
    
          PShape node() {  //stripped down version
            nodeShape = createShape(BOX, 10, 10, 3.5);
            return nodeShape;
          }
        }
    
    
        //-------------------------------->>> NODE PICKER FOR INTERACTION
        Node pickCube(int x, int y) 
        {
          Node cube = null;
    
          pg.beginDraw();
          pg.camera.set(((PGraphicsOpenGL)g).camera);
          pg.projection.set(((PGraphicsOpenGL)g).projection);
          pg.noLights();
          pg.noStroke();
          pg.background(255); 
          for (int i = 0; i < nodes.length; i++)  nodes[i].displayForPicker(); 
          int c = pg.get(x, y);
          pg.endDraw();
    
          for (int i = 0; i < nodes.length; i++) 
          {
            if (nodes[i].pickCol == c) 
            {
              cube = nodes[i];
              break;
            }
          }
          return cube;
        }
    
        public void deselectAllNodes() {
          for ( int n = 0; n < nodes.length; n++) {   
            nodes[n].Active = false;
          }
        }
    
        //------------------------------------------->> Node / Cube Position Array <<<------------------
        // just used to preset cube positions
        char [][] nodeVerts = {  // just a big list of coordinates...ZZzzz
          {128, 19, 59}, 
          {159, 19, 82}, 
          {191, 19, 106}, 
          {179, 19, 143}, 
          {167, 19, 181}, 
          {128, 19, 181}, 
          {88, 19, 181}, 
          {76, 19, 143}, 
          {64, 19, 106}, 
          {96, 19, 82}, 
          {128, 45, 36}, 
          {159, 40, 49}, 
          {191, 40, 72}, 
          {214, 45, 98}, 
          {211, 40, 133}, 
          {199, 40, 170}, 
          {181, 44, 200}, 
          {147, 39, 208}, 
          {108, 39, 208}, 
          {74, 44, 200}, 
          {56, 39, 170}, 
          {44, 40, 133}, 
          {41, 45, 98}, 
          {64, 40, 72}, 
          {96, 40, 49}, 
          {128, 71, 13}, 
          {159, 66, 26}, 
          {191, 61, 39}, 
          {214, 66, 65}, 
          {236, 71, 91}, 
          {233, 65, 126}, 
          {231, 60, 160}, 
          {213, 65, 189}, 
          {195, 70, 219}, 
          {161, 65, 227}, 
          {128, 60, 235}, 
          {94, 65, 227}, 
          {60, 70, 219}, 
          {42, 65, 189}, 
          {24, 60, 160}, 
          {22, 65, 126}, 
          {19, 71, 91}, 
          {41, 66, 65}, 
          {64, 61, 39}, 
          {96, 66, 26}, 
          {147, 100, 9}, 
          {179, 94, 22}, 
          {211, 94, 45}, 
          {233, 99, 72}, 
          {246, 99, 109}, 
          {243, 94, 143}, 
          {231, 94, 181}, 
          {212, 99, 209}, 
          {181, 98, 234}, 
          {147, 93, 242}, 
          {108, 93, 242}, 
          {74, 98, 234}, 
          {42, 99, 210}, 
          {24, 94, 181}, 
          {12, 94, 143}, 
          {9, 99, 109}, 
          {22, 99, 72}, 
          {44, 94, 45}, 
          {76, 94, 22}, 
          {108, 100, 9}, 
          {128, 128, 6}, 
          {167, 128, 6}, 
          {199, 128, 29}, 
          {231, 128, 52}, 
          {243, 128, 89}, 
          {255, 128, 127}, 
          {243, 127, 164}, 
          {231, 127, 202}, 
          {199, 127, 225}, 
          {167, 127, 248}, 
          {128, 127, 248}, 
          {88, 127, 248}, 
          {56, 127, 225}, 
          {24, 127, 202}, 
          {12, 127, 164}, 
          {0, 128, 127}, 
          {12, 128, 89}, 
          {24, 128, 52}, 
          {56, 128, 29}, 
          {88, 128, 6}, 
          {147, 162, 12}, 
          {181, 157, 20}, 
          {213, 156, 43}, 
          {231, 161, 73}, 
          {243, 161, 110}, 
          {246, 156, 145}, 
          {233, 156, 182}, 
          {211, 161, 208}, 
          {179, 161, 232}, 
          {147, 155, 245}, 
          {108, 155, 245}, 
          {76, 161, 232}, 
          {44, 161, 208}, 
          {22, 156, 182}, 
          {9, 156, 145}, 
          {12, 161, 110}, 
          {24, 161, 73}, 
          {42, 156, 43}, 
          {74, 157, 20}, 
          {108, 162, 12}, 
          {128, 195, 19}, 
          {161, 190, 27}, 
          {195, 185, 35}, 
          {213, 190, 64}, 
          {231, 195, 94}, 
          {233, 190, 128}, 
          {236, 184, 162}, 
          {214, 189, 189}, 
          {191, 194, 215}, 
          {159, 189, 228}, 
          {128, 184, 241}, 
          {96, 189, 228}, 
          {64, 194, 215}, 
          {41, 189, 189}, 
          {19, 184, 162}, 
          {22, 190, 128}, 
          {24, 195, 94}, 
          {42, 190, 64}, 
          {60, 185, 35}, 
          {94, 190, 27}, 
          {147, 216, 46}, 
          {181, 211, 54}, 
          {199, 215, 83}, 
          {211, 215, 121}, 
          {214, 210, 155}, 
          {191, 215, 182}, 
          {159, 215, 205}, 
          {128, 210, 218}, 
          {96, 215, 205}, 
          {64, 215, 182}, 
          {41, 210, 155}, 
          {44, 215, 121}, 
          {56, 215, 83}, 
          {74, 211, 54}, 
          {108, 216, 46}, 
          {128, 236, 73}, 
          {167, 236, 73}, 
          {179, 236, 111}, 
          {191, 236, 148}, 
          {159, 236, 171}, 
          {128, 236, 194}, 
          {96, 236, 171}, 
          {64, 236, 148}, 
          {76, 236, 111}, 
          {88, 236, 73}, 
          {147, 246, 100}, 
          {159, 245, 138}, 
          {128, 245, 161}, 
          {96, 245, 138}, 
          {108, 246, 100}, 
          {128, 255, 128}
        } ;
    

    Cheers, mala

  • I've got this to work a bit, but there's still a problem at poles I think... and I've had to bodge some values, very much trial and error.

    You can rotate the globe around Y using LEFT /RIGHT arrow keys and the cubes should update.

    I think the position & rotation values of my cubes are not helping, from what I've been told the phi & theta angles I have used to set the rotation of the cubes (so they face to globe center ) are derived wrongly. But any other way I do it they rotate around their Z axis which I don't want.

    Anyway code above is now updated :)

  • edited April 2018

    This is just some random thought, but couldnt you use ColGet = get(width/2,height/2) in conjunction with rotating the sphere with a rotation around the axis corresponding the hit, so that the hit point would be set at the wanted position and rotating it back? Just like when trying to rotate something around a non origin point? Like if the point you want is at 110, 0,0 you could just rotate the sphere by 90° around the y axis like rotateY(radians(90)) and then get(width/2, height/2) and rotate back rotateY(-radians(90)). This works even if the shape has an unknown rotation, since the point youre looking for would still be at 110,0,0. Now you'd only need to get the corresponding maths behind it. But that would probably be easier to figure out (its basically just the opposite of dragging a sphere(Shape) to rotate it, dont know the math behind it, though)^^. Still, might just be completely wrong^^.

    Edit: actually for rotateY, you could do this : rotateY(x * (90/110) - y * ((90/110) * 2)) Thus if it was 110, 0,0 it would end up with 110(90/110) - 0((90/110)*2) = 90 (note, im not sure if its actually -90, but then just add- before the whole equation) and with lets lay 135° rotation, so, 55, -55, 0 its 135°.) its basically the same for the other ones, but i dont know if all 3 in conjunction really get what you need... well, anyway, hope it helps at least a bit^^ Btw, i just noticed you dont want the z axis, so this approach should definetly work, at least i think so^^ (4th edit... Actually you only need the rotateY if you dont have points on other z positions than 0 anyway. so in code it would be something like this :

    rotateY(x*(90/110)-y*((90/110)*2));
    ColGet = get(width/2, height/2);
    rotateY(-(x(*90/110)-y*((90/110)*2))):
    
Sign In or Register to comment.