How to find distance between two points in a point cloud file

Hi, I am using a pcd loader to load certain points from a pcd file. The file loads correctly using a points material and buffer geometry and adds up to the scene. I have calculated origin that returns a Vector3 using the following:

 getCenter(): THREE.Vector3 {
    const points: any = this.getPointsWithBufferGeometry();
    var geometry = points.geometry;
    geometry.computeBoundingBox();
    var center = new THREE.Vector3();
    geometry.boundingBox.getCenter(center);
    points.localToWorld(center);
    return center;
  }

Now I want to get the distance of each point from the center, I am not sure how to do this as I do not understand how to get the Vector3 coordinate for each point, because the v1.distanceTo(v2) works only when both coordinates are Vector3
This is my code to load the pcd.

      const loader = new PCDLoader();
      loader.load(
        this.url,
        (points: THREE.Points) => {
          if (this.pcdLoaded) {
            return;
          }

          const geometry: THREE.BufferGeometry = <THREE.BufferGeometry>(
            points.geometry
          );
          const material: THREE.PointsMaterial = <THREE.PointsMaterial>(
            points.material
          );
          
          geometry.center();
          geometry.rotateX(Math.PI);
          const f=geometry.attributes.position.array
          geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( f, 3 ) );
          material.side = THREE.DoubleSide;
          material.color.setHex(this.defaults.hex);
          this.pointCloud = points;
          const helper = this.setGridHelper();

          this.grid=helper
          this.scene.add(this.grid);
          this.scene.add(this.pointCloud);
          this.render();
          this.centerOfLidar = this.getCenter();
          const count = geometry.attributes.position.count;
          this.numberOfPointsLoaded = count;
          this.numberOfPointsShowing = this.numberOfPointsLoaded;
          this.pointsCurrentColor = this.defaults.hex;
          this.orbitControls.saveState();
          this.isLoading = false;
          this.event.emit({
            trigger: "lidar-data-loaded-trigger",
            loaded: true,
          });
          this.pcdLoaded = true;
          this.initStats();
        },
        (progress) => {
          this.progress = Math.round((100 * progress.loaded) / progress.total);
        },
        (err) => {
          console.log(err);
          this.isLoading = false;
          this.event.emit({
            trigger: "lidar-data-loaded-trigger",
            loaded: false,
          });
          this.loadingError = true;
        }
      );
    

    //LISTENERS

    this.setInteractiveListeners();

Reference Link : three.js-r139.tar.gz: .../webgl_loader_pcd.html | Fossies

@Mugen87 need your brilliant expertise on this. You are awesome. I follow your solutions and every time they succeed.

Hi!
Distance from the center is the length of a vector: three.js docs

Hi, I understand this, but how to get the Vector3 Coordinates of each point in the cloud, All I see is the array points.geometry.attributes.position.array which has only 1 coordinate for each point. Ref attachment

Ah… Use .fromBufferAttribute() method of Vector3.
Something like this:

let v3 = new THREE.Vector3(); // for re-use
let pos = geometry.attributes.position;
for(let i = 0; i < pos.count; i++){
    v3.fromBufferAttribute(pos, i);
    console.log(v3.length());
}
1 Like

Thank you prisoner849 from your prompt replies. I tried this now:

const temp = new THREE.Vector3(); // for re-use
          let pos: any = geometry.attributes.position;
          for (let i = 0; i < pos.count; i++) {
            this.vector3[i].add(temp.fromBufferAttribute(pos,i))
          }
          console.log(this.vector3)

But throws me an error on trying to add vectors to the i-th position. I intend to use this positions later on for distance calculation from center, so I have used the vector3 as an array of Vectors. How to deal with this please ?

1 Like

Solved the issue by the following snippet using the coordinates pos.getX() and so on to build the iterative vector over 0.5 million points

private evaluateDistanceFromOrigin(geometry: THREE.BufferGeometry) {
    let pos: Float32BufferAttribute = <Float32BufferAttribute>(
      geometry.attributes.position
    );
    let arr: THREE.Vector3[] = [];
    let sampler: any[] = [];
    const center = this.centerOfLidar;// new THREE.Vector3(49729.875, 36680.39453125, 106.71810150146484)// this.centerOfLidar;
    for (let i = 0; i < pos.count; i++) {
      const x = pos.getX(i);
      const y = pos.getY(i);
      const z = pos.getZ(i);
      arr[i] = <THREE.Vector3>{
        x,
        y,
        z,
      };
      const d = center.distanceTo(arr[i]); //  or this.distanceVector(center,arr[i]) can be used to calculate distance from center of lidar 34.34580025866955 34.34580025866955
      const zone: HeatZone = this.determineHeatZone(d);
      sampler[i] = {
        pointCloud: arr[i],
        distance: d,
        zone,
      };
    }
    this.pointCloudSampler = sampler;
  }

public renderBasedOnColorOpt(colorOpt) {
    this.hideKeyBoardShortcuts();
    const points: THREE.Points = <THREE.Points>(
      this.getPointsWithBufferGeometry()
    );
    const material: THREE.PointsMaterial = <THREE.PointsMaterial>(
      points.material
    );
    if (colorOpt === "distance") {
      
      const geometry: THREE.BufferGeometry = <THREE.BufferGeometry>(
        points.geometry
      );
      material.needsUpdate = true;
      material.vertexColors = THREE.VertexColors;
      const colors = [];
      const color = new THREE.Color();
      for (let i = 0; i < this.pointCloudSampler.length; i++) {
        const zoneColor = this.pointCloudSampler[i].zone.color;
        color.setRGB(zoneColor.r, zoneColor.g, zoneColor.b);
        colors.push(color.r, color.g, color.b);
      }
      geometry.setAttribute(
        "color",
        new THREE.Float32BufferAttribute(colors, 3)
      );
      geometry.computeBoundingSphere();
      this.updateCamera();
      this.render();
    } else {
      material.needsUpdate = true;
      material.vertexColors=THREE.NoColors
      material.color.setHex(this.defaults.hex)
      this.updateCamera();
      this.render();
    }
  }

Can anyone now help me to set gradient color instead of solid colors

How does the result have to look like?

@prisoner849 I have got the result as expected, now I want red gradient, yellow gradient and orange gradient in place of solid colors.

If you want to use vertex colors, then it will be something like that (see Gradient class): https://codepen.io/prisoner849/pen/zYpzJQG?editors=0010

There are 4 gradient stops(red, white, yellow, orange)

3 Likes

Thanks for the gradient input @prisoner849 But I am using a PointsMaterial/BufferGeometry combination and with that this method do not work.
I also tried solutions on this link by you

Can you help?

In my example I use BufferGeometry + PointsMaterial and it works :thinking:

let g = new THREE.BufferGeometry().setFromPoints(pts);
...
let m = new THREE.PointsMaterial({size: 0.5, vertexColors: true});

If I only knew the goal. Any explanatory picture with the desired result?

Hi @prisoner849 ok here you go:
so, I loaded a PCD file using PCDLoader class, which rendered me a scene with BufferGeometry and PointsMaterial, where initially all the points are colored in white hex 0xffffff. I applied the following code to set the initial color on the material obtained from the pcd:

material.color.setHex(this.defaults.hex);

And this is how I load my pcd file from a certain url and place the points on a grid.

const loader = new PCDLoader();
    loader.load(
      this.url,
      (points: THREE.Points) => {
        if (this.pcdLoaded) {
          return;
        }

        const geometry: THREE.BufferGeometry = <THREE.BufferGeometry>(
          points.geometry
        );
        const material: THREE.PointsMaterial = <THREE.PointsMaterial>(
          points.material
        );

        geometry.center();
        geometry.rotateX(Math.PI);
        material.side = THREE.DoubleSide;
        material.color.setHex(this.defaults.hex);
        this.pointCloud = points;
        const helper = this.setGridHelper();

        this.grid = helper;
        this.scene.add(this.grid);
        this.scene.add(this.pointCloud);
        this.render();

        this.centerOfLidar = this.getVisualCenterOfPolygon(geometry); // this.getMeanCenter(geometry);
        const count = geometry.attributes.position.count;
        this.numberOfPointsLoaded = count;
        this.numberOfPointsShowing = this.numberOfPointsLoaded;
        this.orbitControls.saveState();
        this.isLoading = false;
        this.event.emit({
          trigger: "lidar-data-loaded-trigger",
          loaded: true,
        });
        this.pcdLoaded = true;
        this.initStats();
        this.evaluateDistanceFromOrigin(geometry);
      },
      (progress) => {
        this.progress = Math.round((100 * progress.loaded) / progress.total);
      },
      (err) => {
        console.log(err);
        this.isLoading = false;
        this.event.emit({
          trigger: "lidar-data-loaded-trigger",
          loaded: false,
        });
        this.loadingError = true;
      }
    );

Until this is simple, now on click of certain button and on certain business logic criteria met, I need to colorize/heatmap the points in the point cloud to 3 parts, such as
Red color gradient for some points
Orange or yellow color gradient for some points
Green color gradients for some points

To do this, I have done the following code taking idea from your lerpColors implementation:


 const points: THREE.Points = <THREE.Points>(
      this.getPointsWithBufferGeometry()
    );

    const material: THREE.PointsMaterial = <THREE.PointsMaterial>(
      points.material
    );

    material.needsUpdate = true;

const geometry: THREE.BufferGeometry = <THREE.BufferGeometry>(
        points.geometry
      );

      material.vertexColors = THREE.VertexColors;
      const colors = [];
      let color = new THREE.Color();

      for (let i = 0; i < this.pointCloudSampler.length; i++) {
        const zoneColorHex: string = this.pointCloudSampler[i].zone.color.hex;
        color.setStyle(zoneColorHex)
        colors.push(color.r, color.g, color.b);
      }

      geometry.setAttribute(
        "color",
        new THREE.Float32BufferAttribute(colors, 3,true)
      );
      geometry.computeBoundingSphere();



private getPointsWithBufferGeometry(): THREE.Object3D {
    return <THREE.Mesh>this.scene.getObjectByProperty("type", "Points");
  }

Here zoneColorHex is obtained by lerping two similar shades of red, orange and green respectively to produce respective gradients. But seems like the lerp is not working as expected to. Here is my lerp colors implementation for the GREEN shade:

export enum HEX {
  GREEN = "#18cd1f",
  RED = "#f4141f",
  ORANGE = "#d78c00",
}
export enum RELATIVE_HEX {
  GREEN = "#10e21f",
  RED = "#dd0044",
  ORANGE = "#e26410",
}
lerpColors(HEX.GREEN, RELATIVE_HEX.GREEN)
export const lerpColors = (colorStart: string, colorEnd: string): string => {
  const gradient = generateColor(colorStart, colorEnd, 10);
  let colorHex: string = "#" + gradient[9];
  return colorHex;
};
export const hex = (c) => {
  var s = "0123456789abcdef";
  var i = parseInt(c);
  if (i == 0 || isNaN(c)) return "00";
  i = Math.round(Math.min(Math.max(0, i), 255));
  return s.charAt((i - (i % 16)) / 16) + s.charAt(i % 16);
};

/* Convert an RGB triplet to a hex string */
export const convertToHex = (rgb) => {
  return hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);
};

/* Remove '#' in color hex string */
export const trim = (s: string) => {
  if (typeof s === "string") return s.charAt(0) == "#" ? s.substring(1, 6) : s;
};

/* Convert a hex string to an RGB triplet */
const convertToRGB = (hex: string) => {
  var color = [];

  color[0] = parseInt(trim(hex).substring(0, 2), 16);
  color[1] = parseInt(trim(hex).substring(2, 4), 16);
  color[2] = parseInt(trim(hex).substring(4, 6), 16);

  return color;
};

export const generateColor = (colorStart, colorEnd, colorCount) => {
  // The beginning of your gradient
  var start = convertToRGB(colorStart);

  // The end of your gradient
  var end = convertToRGB(colorEnd);

  // The number of colors to compute
  var len = colorCount;

  //Alpha blending amount
  var alpha = 0.0;

  var grad = [];

  for (var i = 0; i < len; i++) {
    var c = [];
    alpha += 1.0 / len;

    c[0] = start[0] * alpha + (1 - alpha) * end[0];
    c[1] = start[1] * alpha + (1 - alpha) * end[1];
    c[2] = start[2] * alpha + (1 - alpha) * end[2];

    grad.push(convertToHex(c));
  }

  return grad;
};

The reason why I made my own lerp color to make a gradient is that I am on Angular version 8.2 right now and using three js version 0.110.0 which perhaps does not support lerpColors, but I took the idea of lerping it myself seeing your implementation, but seems like something I missed here to convert the solid color to a gradient.

The output even after lerping two color shades I get is this:


Here you can see I get the colors as solid, they are not gradient-ified :smiley:

But the desired color gradient should appear as this:
Capture

Is this explanation okay for you to help me, pls let me know, I will provide more detail if needed.

Any help on the above comment will really be appreciated. @Mugen87 @prisoner849

Maybe this example will give you some ideas:

Thanks for the idea, really appreciate it, but here you are using ShaderMaterial, so you can use uniforms, I am using PointsMaterial :frowning: @prisoner849

You can use .onBeforeCompile() of PointsMaterial to modify it.
In general, I do it this way:

let uniforms = {
    uniform1: { value: new THREE.Vector3(0, 1, 2) }
}

let m = new THREE.PointsMaterial({
    size: 0.1, 
    onBeforeCompile: shader => {
        shader.uniforms.uniform1 = uniforms.uniform1;
        shader.vertexShader = `
        uniform vec3 uniform1;
        ${shader.vertexShader}
        `.replace(
            `old_part_of_code`,
            `code_with_modification`
        ) // modify vertex shader
        shader.fragmentShader = `...`; // modify fragment shader 
    }
})

... // somewhere later in the code
uniforms.uniform1.value = _new_value_;

Not a very simple example, but the first I recalled: Petals 2020 (for NY contest)

Thank you so much, I will check this out.