Position objects along a Line

I’m creating a network diagram using three. So far I’ve been able to do:

  • Added 2 objects which are my network devices
  • Add a link between the two devices using a line
  • Add device interface labels using canvas and sprites.
  • I’m able to move the devices around the scene and update the line positions

My issue is:

-When adding the interface labels I need to align them on the line between network devices. I want them aligned halfway between the line boundingSphere.center and the network devices which will represent each devices local connecting interface.

NOTE: I’m using dragControls for the scene

Simple Example:

[object]----[label/sprite]------[boundingSpere.center]-----[label/sprite]----[object]

Basic Scene:

The only way ive got close was to create two new lines using the BoundingSphere of the line between network devices and the boundingBox of min/max of the new line. This almost gave me what i need positioning became and issue depending on the line direction.

Code:

        export const setLinkLabels = (linkLabelOne, linkLabelTwo, 
        multiSelect, line, position, postion2) => {

        linkLabelOne.direction = 'from'
        linkLabelOne.to = multiSelect.current[0].uuid
        linkLabelOne.position.x = position.center.x
        linkLabelOne.position.y = position.center.y


        linkLabelTwo.direction = 'to'
        linkLabelTwo.to = multiSelect.current[1].uuid
        linkLabelTwo.position.x = postion2.center.x
        linkLabelTwo.position.y = postion2.center.y

    }
    
    export const canvas = (deviceName) => {

        const canvas = document.createElement('canvas');
        canvas.width = 256;
        canvas.height = 256;
        const ctx = canvas.getContext("2d");

        if (deviceName === 'interface') {
            ctx.font = "20pt Arial"
        } else {
            ctx.font = "33pt Arial"
        }

        ctx.fillStyle = "white";
        ctx.textAlign = "center";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 8;

        if (deviceName !== undefined) {
            ctx.strokeText(deviceName, 128, 46);
            ctx.fillText(deviceName, 128, 46);
        } else {
            ctx.strokeText('device', 128, 46);
            ctx.fillText('device', 128, 46);
        }

        return canvas

    }


    const addLink = (operation) => {
        const points = [];
        const group = new THREE.Group()
        group.isLine = true

        points.push(multiSelect.current[0].position);
        points.push(multiSelect.current[1].position);

        const geometry = new THREE.BufferGeometry().setFromPoints(points);
        const material = new THREE.LineBasicMaterial({color: 0xffffff, depthTest: false});
        const line = new THREE.Line(geometry, material);

        line.links = {from: multiSelect.current[0].uuid, to: multiSelect.current[1].uuid}
        line.renderOrder = 0

        line.geometry.attributes.position.needsUpdate = true;
        line.geometry.computeBoundingBox();
        line.geometry.computeBoundingSphere();
        
        let tempPoints = []
        tempPoints.push(new THREE.Vector3(line.geometry.boundingBox.max.x, line.geometry.boundingBox.max.y, 0));
        tempPoints.push(new THREE.Vector3(line.geometry.boundingSphere.center.x, line.geometry.boundingSphere.center.y, 0));

        const tempGeometry = new THREE.BufferGeometry().setFromPoints(tempPoints);
        const tempMaterial = new THREE.LineBasicMaterial();
        const Templine = new THREE.Line(tempGeometry, tempMaterial);

        Templine.geometry.computeBoundingSphere();

        tempPoints = []
        tempPoints.push(new THREE.Vector3(line.geometry.boundingBox.min.x, line.geometry.boundingBox.min.y, 0));
        tempPoints.push(new THREE.Vector3(line.geometry.boundingSphere.center.x, line.geometry.boundingSphere.center.y, 0));

        const tempGeometry1 = new THREE.BufferGeometry().setFromPoints(tempPoints);
        const tempMaterial1 = new THREE.LineBasicMaterial();
        const Templine1 = new THREE.Line(tempGeometry1, tempMaterial1);
        Templine1.geometry.computeBoundingSphere();
        //
        const linkLabelOne = modifyDeviceName(multiSelect.current[0].interface, 'dynamicBuilder', multiSelect.current[0].position)
        const linkLabelTwo = modifyDeviceName(multiSelect.current[1].interface, 'dynamicBuilder', multiSelect.current[1].position)
        setLinkLabels(linkLabelOne, linkLabelTwo, multiSelect, Templine.geometry.boundingSphere, Templine1.geometry.boundingSphere)
        
        multiSelect.current.push(linkLabelOne)
        multiSelect.current.push(linkLabelTwo)
        group.add(linkLabelOne, linkLabelTwo)

    }

I’m thinking though there has to be a better way if if the code above was modified to work. I’ve thought of getting the line angle and distance to place the labels.

Any nudge in the right direction would help

There’s a lot to unpack here. I think what you’re asking is how to position the label g0/0 along the center of the line and above or below the line?

Should the label always face the camera or is it fixed/aligned to the line? If aligned, then rotating the diagram to look down the line would make the label disappear (since its edge on).

You are correct on what I’m asking, center other line.

I want the labels fixed , always at the same point. When the router/object is dragged, each end of the line is positioned with the new position router/object using the .geometry.attributes.position.array .This works in my program, no issues. I just need the labels there as well.

Also note that I don’t have any other control configured in the scene other than drag controls. This is on purpose.

I think if you make the label a child of line, it should be centered on the line. Then you just need to translate and rotate to how you want to see it. If the line moves, the label should move with it automatically. Scaling the line might be a problem, but you can create an inverse scale for the label to counteract this.

I did a quick test and it appears to work

The following is using angular-three, but it basically translates to the corresponding three calls. When an object is nested, its a child

<ngt-line [position]="[0, 1, -1]">
  <ngt-buffer-geometry (ready)="ready($event)">
  </ngt-buffer-geometry>
  <ngt-line-basic-material></ngt-line-basic-material>

  <ngt-soba-text [text]="'label'"
                 [castShadow]="true"
                 [color]="'white'"
                 [maxWidth]="8"
                 [fontSize]="0.1"
                 font="https://fonts.gstatic.com/s/raleway/v14/1Ptrg8zYS_SKggPNwK4vaqI.woff"
                 anchorX="center"
                 anchorY="bottom"
                 >
  </ngt-soba-text>
</ngt-line>
  ready(buffer: BufferGeometry) {
    const points = [
      new Vector3(-1, 0, 0),
      new Vector3(1, 0, 0)
    ];
    buffer.setFromPoints(points);
  }

Hope this helps

Im going to give this a shot shortly

I got this to a point to where is acceptable right now. Now I will work on repositioning. I set the labels as the child of the line. Unfortunately the labels aren’t updating as expected or as I understand it being children. I’m actually updating the positions manually. As you can see the labels are flipping when the route/object is dragged to a certain point.

My only guess is that I’m updating the line bufferGeometry points and not the position attributes. In turn making the children not update.

const positionsTo = lineUuid[i].geometry.attributes.position.array
positionsTo[3] = event.object.position.x
positionsTo[4] = event.object.position.y
positionsTo[5] = 0

lineUuid[i].geometry.attributes.position.needsUpdate = true;
lineUuid[i].geometry.computeBoundingBox();
lineUuid[i].geometry.computeBoundingSphere();

As of now im just updating the labels manually.

		
	export const setLinkLabels = (linkLabelOne, linkLabelTwo, multiSelect, line) => {

		const center = line.geometry.boundingBox.getCenter(line.geometry.boundingSphere.center)

		line.geometry.boundingBox.copy(line.geometry.boundingBox).applyMatrix4(line.matrixWorld)

		const dir = line.geometry.boundingBox.max.clone().sub(center).normalize().multiplyScalar(.85)

		linkLabelOne.position.set(center.clone().add(dir).x, center.clone().add(dir).y, 0)
		const dir2 = line.geometry.boundingBox.min.clone().sub(center).normalize().multiplyScalar(.85)


		linkLabelTwo.position.set(center.clone().add(dir2).x, center.clone().add(dir2).y, 0)

	}