Work on compass

This commit is contained in:
Fuhrmann 2025-03-19 13:56:08 +01:00
parent b94151bc01
commit 63fc0d1187
4 changed files with 176 additions and 65 deletions

View file

@ -25,8 +25,8 @@ export default function Home() {
</button> </button>
</div> </div>
<div className="flex-1 flex min-h-0 h-full"> <div className="flex-1 flex min-h-0 h-full">
<div className="flex-1"> <div className="relative flex-1">
<div className="hidden sm:block absolute top-2 left-2"> <div className="hidden sm:block absolute top-2 right-2">
<ResetView></ResetView> <ResetView></ResetView>
</div> </div>
<Map></Map> <Map></Map>

View file

@ -19,7 +19,10 @@ import {
Orientation, Orientation,
buildClippingplanes, buildClippingplanes,
} from "./utils/build-clipping-planes"; } from "./utils/build-clipping-planes";
import { buildCoordinateGrid } from "./utils/build-coordinate-grid"; import {
buildCoordinateGrid,
buildHeightGrid,
} from "./utils/build-coordinate-grid";
import { import {
DragControls, DragControls,
OBJExporter, OBJExporter,
@ -371,9 +374,15 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
// Add a coordinate grid to the scene // Add a coordinate grid to the scene
const { gridHelper, annotations } = buildCoordinateGrid(extent); const { gridHelper, annotations } = buildCoordinateGrid(extent);
const { heightGridHelper, heightAnnotations } = buildHeightGrid(extent);
const annotationsGroup = new Group(); const annotationsGroup = new Group();
annotationsGroup.name = "coordinate-grid"; annotationsGroup.name = "coordinate-grid";
annotationsGroup.add(...annotations, gridHelper); annotationsGroup.add(
...annotations,
gridHelper,
...heightAnnotations,
heightGridHelper
);
annotationsGroup.visible = false; annotationsGroup.visible = false;
scene.add(annotationsGroup); scene.add(annotationsGroup);

View file

@ -1,118 +1,157 @@
import { import {
BufferGeometry,
CanvasTexture, CanvasTexture,
GridHelper, Group,
Line,
LineBasicMaterial,
Sprite, Sprite,
SpriteMaterial, SpriteMaterial,
Vector4, Vector3,
} from "three"; } from "three";
import { Extent } from "./build-scene"; import { Extent } from "./build-scene";
import { getCenter3D } from "./utils";
enum Orientation { enum Orientation {
Horizontal, X,
Vertical, Y,
Z,
} }
export function buildCoordinateGrid(extent: Extent) { export function buildCoordinateGrid(extent: Extent) {
const center = getCenter3D(extent);
// Calculate the width and height of the grid // Calculate the width and height of the grid
const gridWidth = extent.xmax - extent.xmin; const gridWidth = extent.xmax - extent.xmin;
const gridHeight = extent.ymax - extent.ymin; const gridHeight = extent.ymax - extent.ymin;
// Decide on the number of divisions (e.g., 20 divisions along each axis) // Decide on the number of divisions
const divisions = 20; const divisions = 10;
// Create a grid helper with the calculated grid size and divisions const xOffset = gridWidth / divisions;
const gridHelper = new GridHelper(Math.max(gridWidth, gridHeight), divisions); let x = extent.xmin - xOffset;
const xPairs = [];
// Position the grid in the scene to match the given extent for (let i = 0; i < divisions + 1; i++) {
gridHelper.position.set(center.x, center.y, 0); x += xOffset;
const start = new Vector3(x, extent.ymin, extent.zmin);
// Rotate the grid to align with the XY-plane const end = new Vector3(x, extent.ymax, extent.zmin);
gridHelper.rotation.x = Math.PI / 2; xPairs.push([start, end]);
// 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 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 = []; const annotations = [];
for (const point of startingPointsHorizontal) { for (let i = 0; i < xPairs.length - 1; i++) {
const label = createLabel( const [start, _] = xPairs[i];
`${point.x.toFixed(2)}`, const label = createLabel(`${start.x.toFixed(0)}m`, start, Orientation.X);
point,
Orientation.Horizontal
);
annotations.push(label); annotations.push(label);
} }
for (const point of startingPointsVertical) { for (let i = 0; i < yPairs.length - 1; i++) {
const label = createLabel( const [start, _] = yPairs[i];
`${point.y.toFixed(2)}`, const label = createLabel(`${start.y.toFixed(0)}m`, start, Orientation.Y);
point,
Orientation.Vertical
);
annotations.push(label); annotations.push(label);
} }
const gridHelper = new Group();
gridHelper.add(...xLines, ...yLines);
return { gridHelper, annotations }; 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 to create annotation (sprite with text)
function createLabel( function createLabel(
text: string, text: string,
position: Vector4, position: Vector3,
orientation: Orientation orientation: Orientation
) { ) {
const spriteMaterial = new SpriteMaterial({ const spriteMaterial = new SpriteMaterial({
map: new CanvasTexture(generateTextCanvas(text, orientation)), // Create text texture map: new CanvasTexture(generateTextCanvas(text)), // Create text texture
transparent: true, transparent: true,
}); });
const sprite = new Sprite(spriteMaterial); const sprite = new Sprite(spriteMaterial);
// Set position according to axis orientation // Set position according to axis orientation
if (orientation === Orientation.Horizontal) { if (orientation === Orientation.X) {
sprite.position.set(position.x + 1000, position.y - 1500, position.z + 500); 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 { } 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; return sprite;
} }
// Function to generate a text canvas for the annotation // 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 canvas = document.createElement("canvas");
const context = canvas.getContext("2d"); const context = canvas.getContext("2d");
if (context) { if (context) {
canvas.width = 800; const width = 800;
canvas.height = 160; const height = 200;
canvas.width = width;
canvas.height = height;
// Set the text style // Set the text style
context.font = "45px Arial"; context.font = `${height - 30}px Arial`;
context.fillStyle = "black"; context.fillStyle = "black";
if (orientation === Orientation.Horizontal) { context.fillText(text, 0, height - 15);
//context.fillText(text, 300, 160);
context.fillText(text, 100, 90);
} else {
context.fillText(text, 100, 90);
}
} }
return canvas; 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;
}

View file

@ -6,6 +6,13 @@ import {
DirectionalLight, DirectionalLight,
Group, Group,
Object3D, Object3D,
AxesHelper,
OrthographicCamera,
Camera,
CanvasTexture,
SpriteMaterial,
Sprite,
Euler,
} from "three"; } from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@ -24,6 +31,9 @@ let controls: OrbitControls;
let renderer: WebGLRenderer; let renderer: WebGLRenderer;
let camera: PerspectiveCamera; let camera: PerspectiveCamera;
let scene: Scene; let scene: Scene;
let axesHelper: AxesHelper;
let uiCamera: Camera;
let uiScene: Scene;
export function buildScene(container: HTMLElement, extent: Extent) { export function buildScene(container: HTMLElement, extent: Extent) {
const maxSize = getMaxSize(extent); const maxSize = getMaxSize(extent);
const center = getCenter3D(extent); const center = getCenter3D(extent);
@ -61,6 +71,7 @@ export function buildScene(container: HTMLElement, extent: Extent) {
controls = new OrbitControls(camera, renderer.domElement); controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(center.x, center.y, center.z); // Focus on the center controls.target.set(center.x, center.y, center.z); // Focus on the center
controls.enableDamping = true; // Smooth camera movement controls.enableDamping = true; // Smooth camera movement
controls.dampingFactor = 0.1;
controls.maxDistance = maxSize * 3; controls.maxDistance = maxSize * 3;
controls.minDistance = maxSize / 5; controls.minDistance = maxSize / 5;
controls.update(); controls.update();
@ -73,6 +84,27 @@ export function buildScene(container: HTMLElement, extent: Extent) {
// Add lights to the scene // Add lights to the scene
buildDefaultLights(scene, extent); 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 }; return { renderer, scene, camera, controls };
} }
@ -88,7 +120,16 @@ function onWindowResize(container: HTMLElement) {
} }
function animate() { 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.render(scene, camera);
renderer.autoClear = false;
renderer.render(uiScene, uiCamera); // Render UI scene
// required if controls.enableDamping or controls.autoRotate are set to true // required if controls.enableDamping or controls.autoRotate are set to true
controls.update(); controls.update();
@ -131,3 +172,25 @@ function buildDefaultLights(scene: Scene, extent: Extent) {
lightsGroup.add(...lights); lightsGroup.add(...lights);
scene.add(lightsGroup); 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;
}