Adding Index Numbers to Landmark Coordinates in Three.js

Hello everyone,

I am currently working on a Three.js project where I have a set of landmarks, each defined by their XYZ coordinate values. These landmarks are stored in an array, and I am displaying them on my 3D model.

What I am trying to achieve now is to add the index number from the array to the displayed landmark on the model, so that each landmark’s position in the array is clearly visible on the screen.

Here is a bit more detail about what I have:

  1. Landmark Array: An array of objects, where each object represents a landmark with XYZ coordinates.
  2. Displaying Landmarks: I am using Three.js to display these landmarks on my 3D model.

And here is what I need help with:

  • Adding Index Numbers: How can I take the index of each landmark from the array and display it next to the corresponding landmark on the model? I want the index number to be visible so that one can easily identify the order of the landmarks.

What did I expected result

My code is not working. There is an error with fontLoader
Uncaught (in promise) SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data

function addLandmarksToScene(landmarks: THREE.Vector3[]) {
    const sphereGeometry = new THREE.SphereGeometry(0.005);
    const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });

    landmarks.forEach((landmark, index) => {
      const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
      sphere.position.copy(landmark);
      scene.add(sphere);

      addTextToScene(index.toString(), landmark);
    });
  }

  function addTextToScene(text: string, position: THREE.Vector3) {
    const loader = new FontLoader();
    console.log({ loader });
    loader.load(HelvetikerFontPath, function (font) {
      console.log({ font });
      const textGeometry = new TextGeometry(text, {
        font: font,
        size: 0.01,
        height: 0.001,
      });
      const geometry = new THREE.TextBufferGeometry('title', { font });

      const textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
      const textMesh = new THREE.Mesh(textGeometry, textMaterial);

      textMesh.position.copy(position).add(new THREE.Vector3(0.01, 0.01, 0)); 
      scene.add(textMesh);
    });
  }

Any code snippets, guidance, or resources that could help me figure this out would be greatly appreciated.

Thank you in advance for your time and assistance!

Ok. From what i understand, you have 3D coordinates, and you want a number displayed next to each coordinate ? Easiest way is to organize your coordinate array, so that the assigned numbers match up with the index.


const coordinates = [
  { x: 0, y: 0, z: 0 },
  // Other coordinates
];


function createText(number, position) {
  const loader = new FontLoader();

  loader.load('/font.json', function (font) {
    const textGeometry = new TextGeometry(number.toString(), {
      font: font,
      size: 0.01,
      height: 0.001,
    });

    const textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
    const textMesh = new THREE.Mesh(textGeometry, textMaterial);

    textMesh.position.copy(position);

    scene.add(textMesh);
  });
}

numberAmount = 20;

for (let i = 0; i < coordinates.length && i < numberAmount; i++) {
  createText(i + 1, new THREE.Vector3(coordinates[i].x, coordinates[i].y, coordinates[i].z));
}

Thank you for your answer!
I have a question about the code you provided. When importing FontLoader, is it correct to import and use it like this? I used svelte with typescript.

import { FontLoader } from 'three/addons/loaders/FontLoader.js';

function createText(number, position) {
  const loader = new FontLoader();

  loader.load('/font.json', function (font) {
    const textGeometry = new THREE.TextGeometry(number.toString(), {
      font: font,
      size: 0.01,
      height: 0.001,
    });

    const textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
    const textMesh = new THREE.Mesh(textGeometry, textMaterial);
    textMesh.position.copy(position);
    scene.add(textMesh);
  });
}

Additionally, in the line loader.load('/font.json', , is it correct to think that you are adding a font file in the root directory of the project and importing it?

This is my whole code.
I am working on a web application where I first render an OBJ model, and then I want the user to be able to upload a landmark file. Once uploaded, I intend to display the landmark coordinates as points on the OBJ model, along with their respective index numbers. Is there anything that I might be missing or overlooking in implementing this functionality?

function handleLandmarkFileChange(event: Event): void {
    const target = event.target as HTMLInputElement;
    if (target.files) {
      const landmarkFile = target.files[0];
      if (landmarkFile) {
        const reader = new FileReader();
        reader.onload = function (e: ProgressEvent<FileReader>) {
          if (e.target?.result) {
            const landmarksArray = JSON.parse(e.target.result as string);

            const landmarks = landmarksArray.map(
              (landmark: any) =>
                new THREE.Vector3(landmark.x, landmark.y, landmark.z)
            );

            addLandmarksToScene(landmarks);
          }
        };
        reader.readAsText(landmarkFile);
      }
    }
  }

  function addLandmarksToScene(landmarks: THREE.Vector3[]) {
    const sphereGeometry = new THREE.SphereGeometry(0.005);
    const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });

    landmarks.forEach((landmark, index) => {
      const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
      sphere.position.copy(landmark);
      scene.add(sphere);

      createText(
        index + 1,
        landmark.clone().add(new THREE.Vector3(0.01, 0, 0))
      );
    });
  }

  function createText(number, position) {
    const loader = new FontLoader();

    loader.load('/font.json', function (font) {
      const textGeometry = new THREE.TextGeometry(number.toString(), {
        font: font,
        size: 0.01,
        height: 0.001,
      });

      const textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
      const textMesh = new THREE.Mesh(textGeometry, textMaterial);
      textMesh.position.copy(position);
      scene.add(textMesh);
    });
  }

Copy paste the font to root, or:

const font = loader.load(
	'fonts/helvetiker_bold.typeface.json',

But if I remember correct, there was more than one font type somewhere in the font folder. How do your users add landmarks ? Im fairly certain you need to use raycasting, and then .map as you did there, since currently they are taken from the file.

1 Like

By placing the font file under the public folder, it worked just as I wanted! Thank you for your help! Have a great day!