GeotiefExplore/src/js/controls/NorthArrow.js
2021-05-06 14:58:12 +02:00

393 lines
No EOL
15 KiB
JavaScript

import { Control } from "./Control";
import { Group } from 'three/src/objects/Group';
import { Vector3 } from 'three/src/math/Vector3';
import { ArrowHelper } from 'three/src/helpers/ArrowHelper';
import * as util from '../core/utilities';
import * as dom from '../core/domUtil';
import * as domEvent from '../core/domEvent';
import { WebGLRenderer } from 'three/src/renderers/WebGLRenderer';
import { Scene } from 'three/src/scenes/Scene';
import { PerspectiveCamera } from 'three/src/cameras/PerspectiveCamera';
import { BoxGeometry } from '../core/BoxGeometry';
import { UpdatableBoxGeometry } from '../clip/UpdatableBoxGeometry';
import { Mesh } from 'three/src/objects/Mesh';
import * as material from '../clip/material';
import { Texture } from 'three/src/textures/Texture';
import { MeshBasicMaterial } from 'three/src/materials/MeshBasicMaterial';
import { DoubleSide } from 'three/src/constants';
import './NorthArrow.css';
export class NorthArrow extends Control {
renderer;
_mainMap;
_scene;
_camera;
_center;
objectGroup;
labels = [];
options = {
position: 'bottomleft',
width: 100,
height: 100,
headLength: 1,
headWidth: 1,
};
constructor(options) {
super(options);
this.objectGroup = new Group();
this.objectGroup.visible = true;
util.setOptions(this, options);
}
onAdd(map) {
this._mainMap = map;
let container = this._container = dom.createDom("div", { "id": "inset", "class": "gba-control-minimap" });
this._container.style.width = this.options.width + 'px';
this._container.style.height = this.options.height + 'px';
domEvent.disableClickPropagation(this._container);
domEvent.on(this._container, 'mousewheel', domEvent.stopPropagation);
let renderer = this.renderer = new WebGLRenderer({ alpha: true });
renderer.setSize(this.options.width, this.options.height);
container.appendChild(renderer.domElement);
this._scene = new Scene();
this._camera = new PerspectiveCamera(30, this.options.width / this.options.height, 0.1, 10000);
this._camera.lookAt(map.center);
const camDirection = new Vector3(-0.5, -Math.SQRT1_2, 0.5);
// const camDirection = new Vector3(0, 0, 1);
const camOffset = camDirection.multiplyScalar(map.size * 2);
this._camera.position.copy(map.center);
this._camera.position.add(camOffset);
this._camera.lookAt(map.center);
this._camera.up = this._map.camera.up;
this._camera.updateProjectionMatrix();
this._createArrow(this._scene);
// this._buildLabels();
return container;
}
animate() {
// this._camera.position.copy(this._map.camera.position);
// // this._camera.position.normalize().multiplyScalar(100);
// // this._camera.position.setLength(this.oldLength);
// // this._camera.up = this._map.camera.up;
// this._camera.lookAt(this._map.center);
this._camera.position.copy(this._map.camera.position);
this._camera.position.sub(this._map.target);
this._camera.position.setLength(35);
this._camera.lookAt(this._scene.position);
// this._camera.near =10;
// this._camera.far = 1000 * 25;
// this._camera.lookAt(this._map.center);
// this._camera.updateProjectionMatrix();
this.renderer.render(this._scene, this._camera);
// this._updateInsetLabelPositions();
}
_createArrow(app_scene, size = 6) {
let from = new Vector3(0, 0, 0);
let headLength = this.options.headLength;//1;
let headWidth = 1;//this.options.headWidth;//1;
let xTo = new Vector3(1, 0, 0);
// let xTo = new Vector3(from.x + 1, from.y, from.z);
// let xDirection = xTo.clone().sub(from);
//(this.objectGroup.add(new ArrowHelper(xTo, from, this._map.size * 0.5, 0xf00000, headLength, headWidth)); // Red = x
this.objectGroup.add(new ArrowHelper(xTo, from, size, 0xff0000, headLength, headWidth)); // Red = x
let yTo = new Vector3(0, 1, 0);
// let yTo = new Vector3(from.x, from.y + 1, from.z);
// let yDirection = yTo.clone().sub(from);
this.objectGroup.add(new ArrowHelper(yTo, from, size, 0x3ad29f, headLength, headWidth)); // Green = y
let zTo = new Vector3(0, 0, 1);//blue z
// let zTo = new Vector3(from.x, from.y, from.z + 1);
// let zDirection = zTo.clone().sub(from);
this.objectGroup.add(new ArrowHelper(zTo, from, size, 0x6b716f, headLength, headWidth)); //8 is the length, Gray = z; 20 and 10 are head length and width
// let spritey = this._makeTextSprite(
// "top",
// { fontsize: 32, backgroundColor: { r: 255, g: 100, b: 100, a: 1 } }
// );
// // spritey.position.set(2.5, 2.5, 5);
// this.objectGroup.add(spritey);
// let myText = this.sprite = new SpriteText('top', 2);
// myText.position.set(2.5, 2.5, 6);
// this.objectGroup.add(myText);
let eastTexture = this._makeTextTexture("E", 0.6, 'rgba(0,0,0,1)');
let eastMaterial = new MeshBasicMaterial({
transparent: true,
// color: 0x6f6f6f,
side: DoubleSide,
map: eastTexture,
// wireframe: true
});
let westTexture = this._makeTextTexture("W", 0.6, 'rgba(0,0,0,1)');
let westMaterial = new MeshBasicMaterial({
transparent: true,
side: DoubleSide,
map: westTexture
});
let northTexture = this._makeTextTexture("N", 0.6, 'rgba(0,0,0,1)');
let northMaterial = new MeshBasicMaterial({
transparent: true,
side: DoubleSide,
map: northTexture
});
let southTexture = this._makeTextTexture("S", 0.6, 'rgba(0,0,0,1)');
let southMaterial = new MeshBasicMaterial({
transparent: true,
side: DoubleSide,
map: southTexture
});
let topTexture = this._makeTextTexture("top", 0.6, 'rgba(0,0,0,1)');
let topMaterial = new MeshBasicMaterial({
transparent: true,
side: DoubleSide,
map: topTexture
});
//add orientation box:
let vertices = [
new Vector3(0, 0, 0), new Vector3(5, 0, 0),
new Vector3(0, 5, 0), new Vector3(5, 5, 0),
new Vector3(0, 0, 5), new Vector3(5, 0, 5),
new Vector3(0, 5, 5), new Vector3(5, 5, 5)
];
this.boxGeometry = new UpdatableBoxGeometry(vertices);
this.boxMesh = new Mesh(this.boxGeometry,
[southMaterial, material.BoxBackFace, eastMaterial, westMaterial, northMaterial, topMaterial]);
// this.boxGeometry = new BoxGeometry(5, 5, 5);
// this.boxMesh = new Mesh(this.boxGeometry,
// [eastMaterial, westMaterial, northMaterial, southMaterial, topMaterial, material.BoxBackFace]);
// this.boxMesh.position.set(2.5, 2.5, 2.5);
this.objectGroup.add(this.boxMesh);
if (app_scene) {
app_scene.add(this.objectGroup);
}
}
_makeTextTexture(message, textHeight = 0.6, color = 'rgba(0,0,0,1)') {
let text = `${message}`;
// this.textHeight = textHeight;
// this.color = color;
let backgroundColor = 'rgba(248,248,255,0.8)'; // no background color
let _padding = 3;
let borderWidth = 0;
let _borderRadius = 0;
let borderColor = 'white';
let strokeWidth = 0;
let strokeColor = 'white';
let fontFace = 'Arial';
let fontSize = 100; // defines text resolution
let fontWeight = 'normal';
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const border = Array.isArray(borderWidth) ? borderWidth : [borderWidth, borderWidth]; // x,y border
const relBorder = border.map(b => b * fontSize * 0.1); // border in canvas units
const borderRadius = Array.isArray(_borderRadius) ? _borderRadius : [_borderRadius, _borderRadius, _borderRadius, _borderRadius]; // tl tr br bl corners
const relBorderRadius = borderRadius.map(b => b * fontSize * 0.1); // border radius in canvas units
const padding = Array.isArray(_padding) ? _padding : [_padding, _padding]; // x,y padding
const relPadding = padding.map((p) => p * fontSize * 0.1); // padding in canvas units
const lines = text.split('\n');
const font = `${fontWeight} ${fontSize}px ${fontFace}`;
ctx.font = font; // measure canvas with appropriate font
const innerWidth = Math.max(...lines.map(line => ctx.measureText(line).width));
const innerHeight = fontSize * lines.length;
canvas.width = innerWidth + relBorder[0] * 2 + relPadding[0] * 2;
canvas.height = innerHeight + relBorder[1] * 2 + relPadding[1] * 2;
// ctx.fillStyle = "rgba( 0, 0, 0, 0 )"; // transparent
// ctx.fillRect( 0, 0, innerWidth, innerHeight );
// paint background
if (backgroundColor) {
ctx.fillStyle = backgroundColor;
if (!_borderRadius) {
ctx.fillRect(relBorder[0], relBorder[1], canvas.width - relBorder[0] * 2, canvas.height - relBorder[1] * 2);
} else { // fill with rounded corners
ctx.beginPath();
ctx.moveTo(relBorder[0], relBorderRadius[0]);
[
[relBorder[0], relBorderRadius[0], canvas.width - relBorderRadius[1], relBorder[1], relBorder[1], relBorder[1]], // t
[canvas.width - relBorder[0], canvas.width - relBorder[0], canvas.width - relBorder[0], relBorder[1], relBorderRadius[1], canvas.height - relBorderRadius[2]], // r
[canvas.width - relBorder[0], canvas.width - relBorderRadius[2], relBorderRadius[3], canvas.height - relBorder[1], canvas.height - relBorder[1], canvas.height - relBorder[1]], // b
[relBorder[0], relBorder[0], relBorder[0], canvas.height - relBorder[1], canvas.height - relBorderRadius[3], relBorderRadius[0]], // t
].forEach(([x0, x1, x2, y0, y1, y2]) => {
ctx.quadraticCurveTo(x0, y0, x1, y1);
ctx.lineTo(x2, y2);
});
ctx.closePath();
ctx.fill();
}
}
// ctx.font = "Bold 10px Arial";
// ctx.fillStyle = "rgba(255,0,0,1)";
// ctx.textAlign = 'center';
// ctx.textBaseline = 'middle';
// ctx.fillText('Hello, world!', 5, 5);
ctx.translate(...relBorder);
ctx.translate(...relPadding);
// paint text
ctx.font = font; // Set font again after canvas is resized, as context properties are reset
ctx.fillStyle = color;
ctx.textBaseline = 'bottom';
const drawTextStroke = strokeWidth > 0;
if (drawTextStroke) {
ctx.lineWidth = strokeWidth * fontSize / 10;
ctx.strokeStyle = strokeColor;
}
lines.forEach((line, index) => {
const lineX = (innerWidth - ctx.measureText(line).width) / 2;
const lineY = (index + 1) * fontSize;
drawTextStroke == true && ctx.strokeText(line, lineX, lineY);
ctx.fillText(line, lineX, lineY);
});
// canvas contents will be used for a texture
let texture = new Texture(canvas)
texture.needsUpdate = true;
return texture;
}
_buildLabels() {
let f = [
{ a: ["x"], cl: "red-label", centroid: [[8, 0, 0]] },
{ a: ["y"], cl: "green-label", centroid: [[0, 8, 0]] },
{ a: ["z"], cl: "gray-label", centroid: [[0, 0, 8]] }
];
let getPointsFunc = function (f) { return f.centroid; };
// create parent element for labels
var e = document.createElement("div");
this._container.appendChild(e);
e.style.display = (this.objectGroup.visible) ? "block" : "none";
let labelParentElement = this.labelParentElement = e; //lable parent div for this layer
for (let i = 0, l = f.length; i < l; i++) {
let labelInfo = f[i];
// labelInfo.aElems = [];
// labelInfo.aObjs = [];
let text = labelInfo.a[0];
if (text === null || text === "") continue;
let classLabel = labelInfo.cl;
if (classLabel === undefined || classLabel === "") classLabel = "label";
let pts = getPointsFunc(labelInfo);
for (let j = 0, m = pts.length; j < m; j++) {
let pt = pts[j];
// create div element for label
let e = document.createElement("div");
e.appendChild(document.createTextNode(text));
e.className = classLabel;// "label";
labelParentElement.appendChild(e);
let pt1 = new Vector3(pt[0], pt[1], pt[2]); // top
this.labels.push({ labelDiv: e, pt: pt1 });
}
}
}
_updateInsetLabelPositions() {
// this.sprite.quaternion.copy(this._camera.quaternion); // to look at camera
let widthHalf = this.options.width / 2;
let heightHalf = this.options.height / 2;
// var autosize = appSettings.Options.label.autoSize;
let camera_pos = this._camera.position;
let target = new Vector3(0, 0, 0);
let c2t = target.sub(camera_pos);
let c2l = new Vector3();
let v = new Vector3();
// make a list of [label index, distance to camera]
let idx_dist = [];
for (let i = 0, l = this.labels.length; i < l; i++) {
idx_dist.push([i, camera_pos.distanceTo(this.labels[i].pt)]);
}
// sort label indexes in descending order of distances
idx_dist.sort(function (a, b) {
if (a[1] < b[1]) return 1;
if (a[1] > b[1]) return -1;
return 0;
});
let label, labelDiv, x, y;
// var minFontSize = appSettings.Options.label.minFontSize;
for (let i = 0, l = idx_dist.length; i < l; i++) {
label = this.labels[idx_dist[i][0]];
labelDiv = label.labelDiv;
if (c2l.subVectors(label.pt, camera_pos).dot(c2t) > 0) {
// label is in front
// calculate label position
v.copy(label.pt);
v.project(this._camera);
x = (v.x * widthHalf) + widthHalf;
y = -(v.y * heightHalf) + heightHalf;
// set label position
labelDiv.style.display = "block";
labelDiv.style.left = (x - (labelDiv.offsetWidth / 2)) + "px";
labelDiv.style.top = (y - (labelDiv.offsetHeight / 2)) + "px";
labelDiv.style.zIndex = i + 1;
}
else {
// label is in back
labelDiv.style.display = "none";
}
}
}
}