import {
  Vector2,
  Vector3,
  Box3,
  Mesh,
  PerspectiveCamera,
  MeshBasicMaterial,
  Scene,
  Color,
  DoubleSide,
  WebGLRenderer,
  BufferGeometry
} from "https://esm.sh/three";
import {
  OrbitControls,
  TextGeometry,
  FontLoader
} from "https://esm.sh/three/addons";

import earcut from "https://esm.sh/earcut";

const skeletonPostProcessingResult = {
  polygons: [
    {
      vertices: [],
      edgeStart: {
        x: 259505.3125,
        y: 6253223.5
      },
      edgeEnd: {
        x: 259521.140625,
        y: 6253168
      }
    },
    {
      vertices: [
        {
          x: 259606.34375,
          y: 6253192.5
        },
        {
          x: 259598.453125,
          y: 6253220.25
        },
        {
          x: 259513.2265625,
          y: 6253195.75
        },
        {
          x: 259521.140625,
          y: 6253168
        }
      ],
      edgeStart: {
        x: 259521.140625,
        y: 6253168
      },
      edgeEnd: {
        x: 259606.34375,
        y: 6253192.5
      }
    },
    {
      vertices: [],
      edgeStart: {
        x: 259606.34375,
        y: 6253192.5
      },
      edgeEnd: {
        x: 259590.5625,
        y: 6253248
      }
    },
    {
      vertices: [
        {
          x: 259505.3125,
          y: 6253223.5
        },
        {
          x: 259513.2265625,
          y: 6253195.75
        },
        {
          x: 259598.453125,
          y: 6253220.25
        },
        {
          x: 259590.5625,
          y: 6253248
        }
      ],
      edgeStart: {
        x: 259590.5625,
        y: 6253248
      },
      edgeEnd: {
        x: 259505.3125,
        y: 6253223.5
      }
    }
  ]
};

const skeletonBox = {
  maxX: 259606.34375,
  maxY: 6253248,
  minX: 259505.3125,
  minY: 6253168 - 20
};

const minHeight = 48;
const height = 10;

const Hmax = 28.85;

const _signedDistanceToLine = function (point, line) {
  const lineVector = new Vector2().subVectors(line[1], line[0]);
  const pointVector = new Vector2().subVectors(point, line[0]);
  const cross = lineVector.x * pointVector.y - lineVector.y * pointVector.x;
  const lineLength = lineVector.length();

  return cross / lineLength;
};

const positions: Vector3[] = [];

for (const polygon of skeletonPostProcessingResult.polygons) {
  const polygonVertices: number[] = [];

  const edgeLine = [
    new Vector2(polygon.edgeStart.x, polygon.edgeStart.y),
    new Vector2(polygon.edgeEnd.x, polygon.edgeEnd.y)
  ];

  for (const vertex of polygon.vertices) {
    polygonVertices.push(vertex.x, vertex.y);
  }
  const triangles = earcut(polygonVertices).reverse();

  for (let i = 0; i < triangles.length; i++) {
    const index = triangles[i];

    const x = polygonVertices[index * 2];
    const y = polygonVertices[index * 2 + 1];
    const vertex = new Vector2(x, y);

    // distance maximale entre le point "vertex" et le vecteur [sommet debut, sommet fin] "edgeLine"
    const dst = _signedDistanceToLine(vertex, edgeLine);
    // Interpolation de l'altitude du point "vertex"
    const vertexZ = minHeight + (height * dst) / Hmax;

    positions.push(new Vector3(x, y, vertexZ));
  }
}

////// THREE.JS SCENE
let camera, scene, renderer, controls;

const loader = new FontLoader();

////// Roof polygons
const material = new MeshBasicMaterial({
  color: "#ffb6e9",
  side: DoubleSide,
  wireframe: false
});

const roofGeometry = new BufferGeometry();
roofGeometry.setFromPoints(positions);

const roofMesh = new Mesh(roofGeometry, material);

const center = new Vector3(
  skeletonBox.minX + (skeletonBox.maxX - skeletonBox.minX) / 2,
  skeletonBox.minY + (skeletonBox.maxY - skeletonBox.minY) / 2,
  minHeight + height / 2
);

function init() {
  renderer = new WebGLRenderer();
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);

  scene = new Scene();
  scene.background = new Color("white");

  camera = new PerspectiveCamera(
    30,
    window.innerWidth / window.innerHeight,
    1,
    10000
  );
  // Our up axes here is the Z
  camera.up.set(0, 0, 1);

  controls = new OrbitControls(camera, renderer.domElement);

  camera.position.set(skeletonBox.maxX, skeletonBox.minY, minHeight * 2);

  camera.lookAt(center);
  controls.target.copy(center);
  controls.update();

  scene.add(roofMesh);

  window.addEventListener("resize", onWindowResize);
}
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  requestAnimationFrame(animate);

  controls.update();

  renderer.render(scene, camera);
}

init();
animate();

loader.load(
  "https://esm.sh/three@0.172.0/examples/fonts/helvetiker_regular.typeface.json",
  function (font) {
    for (let index = 0; index < positions.length; index++) {
      const roofVertice = positions[index];
      const labelMesh = getPointLabelMesh(
        font,
        Math.ceil(roofVertice.z) + " m"
      );
      labelMesh.position.set(roofVertice.x, roofVertice.y, roofVertice.z);
      scene.add(labelMesh);
      //console.log(labelMesh, roofVertice);
    }
  }
);

const getPointLabelMesh = (font, label) => {
  const geometry = new TextGeometry(label, {
    font: font,
    size: 2,
    depth: 0.1
  });
  const labelMesh = new Mesh(
    geometry,
    new MeshBasicMaterial({
      color: "black",
      side: DoubleSide
    })
  );
  labelMesh.rotation.y = Math.PI / 2;
  labelMesh.rotation.z = Math.PI / 2;
  return labelMesh;
};
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.