+
diff --git a/app/three/SceneView.ts b/app/three/SceneView.ts
index db71f91..f627afc 100644
--- a/app/three/SceneView.ts
+++ b/app/three/SceneView.ts
@@ -19,7 +19,10 @@ import {
Orientation,
buildClippingplanes,
} from "./utils/build-clipping-planes";
-import { buildCoordinateGrid } from "./utils/build-coordinate-grid";
+import {
+ buildCoordinateGrid,
+ buildHeightGrid,
+} from "./utils/build-coordinate-grid";
import {
DragControls,
OBJExporter,
@@ -371,9 +374,15 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
// Add a coordinate grid to the scene
const { gridHelper, annotations } = buildCoordinateGrid(extent);
+ const { heightGridHelper, heightAnnotations } = buildHeightGrid(extent);
const annotationsGroup = new Group();
annotationsGroup.name = "coordinate-grid";
- annotationsGroup.add(...annotations, gridHelper);
+ annotationsGroup.add(
+ ...annotations,
+ gridHelper,
+ ...heightAnnotations,
+ heightGridHelper
+ );
annotationsGroup.visible = false;
scene.add(annotationsGroup);
diff --git a/app/three/utils/build-coordinate-grid.ts b/app/three/utils/build-coordinate-grid.ts
index f697cfe..abf686c 100644
--- a/app/three/utils/build-coordinate-grid.ts
+++ b/app/three/utils/build-coordinate-grid.ts
@@ -1,118 +1,157 @@
import {
+ BufferGeometry,
CanvasTexture,
- GridHelper,
+ Group,
+ Line,
+ LineBasicMaterial,
Sprite,
SpriteMaterial,
- Vector4,
+ Vector3,
} from "three";
import { Extent } from "./build-scene";
-import { getCenter3D } from "./utils";
enum Orientation {
- Horizontal,
- Vertical,
+ X,
+ Y,
+ Z,
}
export function buildCoordinateGrid(extent: Extent) {
- const center = getCenter3D(extent);
// Calculate the width and height of the grid
const gridWidth = extent.xmax - extent.xmin;
const gridHeight = extent.ymax - extent.ymin;
- // Decide on the number of divisions (e.g., 20 divisions along each axis)
- const divisions = 20;
+ // Decide on the number of divisions
+ const divisions = 10;
- // Create a grid helper with the calculated grid size and divisions
- const gridHelper = new GridHelper(Math.max(gridWidth, gridHeight), divisions);
-
- // Position the grid in the scene to match the given extent
- gridHelper.position.set(center.x, center.y, 0);
-
- // Rotate the grid to align with the XY-plane
- gridHelper.rotation.x = Math.PI / 2;
-
- // Retrieve the geometry of the grid helper
- const geometry = gridHelper.geometry;
-
- const positionAttr = geometry.getAttribute("position");
- const startingPointsHorizontal = [];
- const startingPointsVertical = [];
- for (let i = 0; i < positionAttr.count; i++) {
- const x = positionAttr.getX(i);
- const z = positionAttr.getZ(i);
- const v = new Vector4(x + center.x, z + center.y, 0, 1);
-
- if (i % 4 === 0) {
- startingPointsVertical.push(v);
- } else if (i % 2 == 0) {
- startingPointsHorizontal.push(v);
- }
+ const xOffset = gridWidth / divisions;
+ let x = extent.xmin - xOffset;
+ const xPairs = [];
+ for (let i = 0; i < divisions + 1; i++) {
+ x += xOffset;
+ const start = new Vector3(x, extent.ymin, extent.zmin);
+ const end = new Vector3(x, extent.ymax, extent.zmin);
+ xPairs.push([start, end]);
}
+ const xLines = createLines(xPairs);
+
+ const yOffset = gridHeight / divisions;
+ let y = extent.ymin - yOffset;
+ const yPairs = [];
+ for (let i = 0; i < divisions + 1; i++) {
+ y += yOffset;
+ const start = new Vector3(extent.xmin, y, extent.zmin);
+ const end = new Vector3(extent.xmax, y, extent.zmin);
+ yPairs.push([start, end]);
+ }
+ const yLines = createLines(yPairs);
const annotations = [];
- for (const point of startingPointsHorizontal) {
- const label = createLabel(
- `${point.x.toFixed(2)}`,
- point,
- Orientation.Horizontal
- );
+ for (let i = 0; i < xPairs.length - 1; i++) {
+ const [start, _] = xPairs[i];
+ const label = createLabel(`${start.x.toFixed(0)}m`, start, Orientation.X);
annotations.push(label);
}
- for (const point of startingPointsVertical) {
- const label = createLabel(
- `${point.y.toFixed(2)}`,
- point,
- Orientation.Vertical
- );
+ for (let i = 0; i < yPairs.length - 1; i++) {
+ const [start, _] = yPairs[i];
+ const label = createLabel(`${start.y.toFixed(0)}m`, start, Orientation.Y);
annotations.push(label);
}
+ const gridHelper = new Group();
+ gridHelper.add(...xLines, ...yLines);
+
return { gridHelper, annotations };
}
+export function buildHeightGrid(extent: Extent) {
+ const gridHeight = extent.zmax - extent.zmin;
+
+ const divisions = 5;
+ const offset = gridHeight / divisions;
+
+ let z = extent.zmin - offset;
+ const pointPairs = [];
+ for (let i = 0; i < divisions + 1; i++) {
+ z += offset;
+ const start = new Vector3(extent.xmax, extent.ymin, z);
+ const end = new Vector3(extent.xmax, extent.ymax, z);
+ pointPairs.push([start, end]);
+ }
+ const lines = createLines(pointPairs);
+
+ const annotations = [];
+ for (const pointPair of pointPairs) {
+ const start = pointPair[0];
+ const label = createLabel(`${start.z.toFixed(0)}m`, start, Orientation.Z);
+ annotations.push(label);
+ }
+
+ const gridHelper = new Group();
+ gridHelper.add(...lines);
+
+ return { heightGridHelper: gridHelper, heightAnnotations: annotations };
+}
+
// Function to create annotation (sprite with text)
function createLabel(
text: string,
- position: Vector4,
+ position: Vector3,
orientation: Orientation
) {
const spriteMaterial = new SpriteMaterial({
- map: new CanvasTexture(generateTextCanvas(text, orientation)), // Create text texture
+ map: new CanvasTexture(generateTextCanvas(text)), // Create text texture
transparent: true,
});
const sprite = new Sprite(spriteMaterial);
// Set position according to axis orientation
- if (orientation === Orientation.Horizontal) {
- sprite.position.set(position.x + 1000, position.y - 1500, position.z + 500);
+ if (orientation === Orientation.X) {
+ sprite.position.set(position.x + 500, position.y - 1500, position.z + 500);
+ } else if (orientation === Orientation.Y) {
+ sprite.position.set(position.x - 3000, position.y + 500, position.z + 500);
} else {
- sprite.position.set(position.x, position.y - 500, position.z + 500);
+ sprite.position.set(position.x + 3000, position.y, position.z + 500);
}
- sprite.scale.set(5000, 2500, 1); // Scale the sprite to make the text readable
+ sprite.scale.set(5000, 2500, 1);
return sprite;
}
// Function to generate a text canvas for the annotation
-function generateTextCanvas(text: string, orientation: Orientation) {
+function generateTextCanvas(text: string) {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (context) {
- canvas.width = 800;
- canvas.height = 160;
+ const width = 800;
+ const height = 200;
+ canvas.width = width;
+ canvas.height = height;
// Set the text style
- context.font = "45px Arial";
+ context.font = `${height - 30}px Arial`;
context.fillStyle = "black";
- if (orientation === Orientation.Horizontal) {
- //context.fillText(text, 300, 160);
- context.fillText(text, 100, 90);
- } else {
- context.fillText(text, 100, 90);
- }
+ context.fillText(text, 0, height - 15);
}
return canvas;
}
+
+function createLines(pointPairs: Vector3[][]) {
+ const lines = [];
+
+ for (const pair of pointPairs) {
+ const geometry = new BufferGeometry().setFromPoints(pair);
+
+ // Line material
+ const material = new LineBasicMaterial({ color: 0x444444 });
+
+ // Create line
+ const line = new Line(geometry, material);
+ lines.push(line);
+ }
+
+ return lines;
+}
diff --git a/app/three/utils/build-scene.ts b/app/three/utils/build-scene.ts
index 61bef77..505ad5e 100644
--- a/app/three/utils/build-scene.ts
+++ b/app/three/utils/build-scene.ts
@@ -6,6 +6,13 @@ import {
DirectionalLight,
Group,
Object3D,
+ AxesHelper,
+ OrthographicCamera,
+ Camera,
+ CanvasTexture,
+ SpriteMaterial,
+ Sprite,
+ Euler,
} from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@@ -24,6 +31,9 @@ let controls: OrbitControls;
let renderer: WebGLRenderer;
let camera: PerspectiveCamera;
let scene: Scene;
+let axesHelper: AxesHelper;
+let uiCamera: Camera;
+let uiScene: Scene;
export function buildScene(container: HTMLElement, extent: Extent) {
const maxSize = getMaxSize(extent);
const center = getCenter3D(extent);
@@ -61,6 +71,7 @@ export function buildScene(container: HTMLElement, extent: Extent) {
controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(center.x, center.y, center.z); // Focus on the center
controls.enableDamping = true; // Smooth camera movement
+ controls.dampingFactor = 0.1;
controls.maxDistance = maxSize * 3;
controls.minDistance = maxSize / 5;
controls.update();
@@ -73,6 +84,27 @@ export function buildScene(container: HTMLElement, extent: Extent) {
// Add lights to the scene
buildDefaultLights(scene, extent);
+ uiScene = new Scene();
+
+ // Create an orthographic camera
+ uiCamera = new OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
+ uiCamera.up.set(0, 0, 1);
+ uiCamera.position.z = 2;
+
+ // Create the AxesHelper (small size)
+ axesHelper = new AxesHelper(0.1);
+ axesHelper.position.set(-0.9, -0.8, 0);
+
+ const xLabel = createTextSprite("X", "red");
+ const yLabel = createTextSprite("Y", "green");
+ const zLabel = createTextSprite("Z", "blue");
+ xLabel.position.set(0.125, 0, 0);
+ yLabel.position.set(0, 0.125, 0);
+ zLabel.position.set(0, 0, 0.125);
+
+ axesHelper.add(xLabel, yLabel, zLabel);
+ uiScene.add(axesHelper);
+
return { renderer, scene, camera, controls };
}
@@ -88,7 +120,16 @@ function onWindowResize(container: HTMLElement) {
}
function animate() {
+ // axesHelper.quaternion.copy(camera.quaternion);
+ let rot = new Euler();
+ rot.x = -camera.rotation.x;
+ rot.y = camera.rotation.y;
+ rot.z = camera.rotation.z;
+ axesHelper.setRotationFromEuler(rot);
+ renderer.autoClear = true;
renderer.render(scene, camera);
+ renderer.autoClear = false;
+ renderer.render(uiScene, uiCamera); // Render UI scene
// required if controls.enableDamping or controls.autoRotate are set to true
controls.update();
@@ -131,3 +172,25 @@ function buildDefaultLights(scene: Scene, extent: Extent) {
lightsGroup.add(...lights);
scene.add(lightsGroup);
}
+
+function createTextSprite(text: string, color = "white") {
+ const canvas = document.createElement("canvas");
+ const ctx = canvas.getContext("2d");
+
+ canvas.width = 256;
+ canvas.height = 128;
+
+ if (ctx) {
+ ctx.fillStyle = color;
+ ctx.font = "24px Arial";
+ ctx.textAlign = "center";
+ ctx.fillText(text, canvas.width / 2, canvas.height / 2);
+ }
+
+ const texture = new CanvasTexture(canvas);
+ const material = new SpriteMaterial({ map: texture });
+ const sprite = new Sprite(material);
+ sprite.scale.set(0.3, 0.15, 1);
+
+ return sprite;
+}