Skip to content

Commit 1e04b2a

Browse files
authored
Improve 2d viewport (#177)
* Prototype better grid * Prototype text on grid * Prototype grid regeneration retry system * Finish up improved 2d scene
1 parent 022b7ff commit 1e04b2a

File tree

7 files changed

+571
-154
lines changed

7 files changed

+571
-154
lines changed

resources/openbim-components.js

Lines changed: 205 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -107734,12 +107734,194 @@ class MiniMap extends Component {
107734107734
MiniMap.uuid = "39ad6aad-84c8-4adf-a1e0-7f25313a9e7f";
107735107735
ToolComponent.libraryUUIDs.add(MiniMap.uuid);
107736107736

107737+
/**
107738+
* An infinite lightweight 2D grid that can be used for any
107739+
* kind of 2d viewports.
107740+
*/
107741+
class Infinite2dGrid {
107742+
constructor(camera, container) {
107743+
this.numbers = new THREE$1.Group();
107744+
this.maxRegenerateRetrys = 4;
107745+
this.gridsFactor = 5;
107746+
this.scaleX = 1;
107747+
this.scaleY = 1;
107748+
this._group = new THREE$1.Group();
107749+
this._frustum = new THREE$1.Frustum();
107750+
this._frustumMat = new THREE$1.Matrix4();
107751+
this._regenerateDelay = 200;
107752+
this._regenerateCounter = 0;
107753+
this._camera = camera;
107754+
this._container = container;
107755+
const main = this.newGrid(0x222222, -1);
107756+
const secondary = this.newGrid(0x111111, -2);
107757+
this.grids = { main, secondary };
107758+
this._group.add(secondary, main, this.numbers);
107759+
}
107760+
get() {
107761+
return this._group;
107762+
}
107763+
dispose() {
107764+
const { main, secondary } = this.grids;
107765+
main.removeFromParent();
107766+
secondary.removeFromParent();
107767+
main.geometry.dispose();
107768+
const mMat = main.material;
107769+
mMat.dispose();
107770+
secondary.geometry.dispose();
107771+
const sMat = secondary.material;
107772+
sMat.dispose();
107773+
}
107774+
regenerate() {
107775+
const isReady = this.isGridReady();
107776+
if (!isReady) {
107777+
this._regenerateCounter++;
107778+
if (this._regenerateCounter > this.maxRegenerateRetrys) {
107779+
throw new Error("Grid could not be regenerated");
107780+
}
107781+
setTimeout(() => this.regenerate, this._regenerateDelay);
107782+
return;
107783+
}
107784+
this._regenerateCounter = 0;
107785+
const matrix = this._frustumMat.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse);
107786+
this._frustum.setFromProjectionMatrix(matrix);
107787+
// Step 1: find out the distance of the visible area of the 2D scene
107788+
// and the translation pixel / 3d unit
107789+
const { planes } = this._frustum;
107790+
const right = planes[0].constant * -planes[0].normal.x;
107791+
const left = planes[1].constant * -planes[1].normal.x;
107792+
const bottom = planes[2].constant * -planes[2].normal.y;
107793+
const top = planes[3].constant * -planes[3].normal.y;
107794+
const horizontalDistance = Math.abs(right - left);
107795+
const verticalDistance = Math.abs(top - bottom);
107796+
const { clientWidth, clientHeight } = this._container;
107797+
const maxPixelDist = Math.max(clientWidth, clientHeight);
107798+
const maxUnit3dDist = Math.max(horizontalDistance, verticalDistance);
107799+
const unit3dPixelRel = maxUnit3dDist / maxPixelDist;
107800+
// Step 2: find out its order of magnitude
107801+
const magnitudeX = Math.ceil(Math.log10(horizontalDistance / this.scaleX));
107802+
const magnitudeY = Math.ceil(Math.log10(verticalDistance / this.scaleY));
107803+
// Step 3: represent main grid
107804+
const sDistanceHor = 10 ** (magnitudeX - 2) * this.scaleX;
107805+
const sDistanceVert = 10 ** (magnitudeY - 2) * this.scaleY;
107806+
const mDistanceHor = sDistanceHor * this.gridsFactor;
107807+
const mDistanceVert = sDistanceVert * this.gridsFactor;
107808+
const mainGridCountVert = Math.ceil(verticalDistance / mDistanceVert);
107809+
const mainGridCountHor = Math.ceil(horizontalDistance / mDistanceHor);
107810+
const secondaryGridCountVert = Math.ceil(verticalDistance / sDistanceVert);
107811+
const secondaryGridCountHor = Math.ceil(horizontalDistance / sDistanceHor);
107812+
// Step 4: find out position of first lines
107813+
const sTrueLeft = sDistanceHor * Math.ceil(left / sDistanceHor);
107814+
const sTrueBottom = sDistanceVert * Math.ceil(bottom / sDistanceVert);
107815+
const mTrueLeft = mDistanceHor * Math.ceil(left / mDistanceHor);
107816+
const mTrueBottom = mDistanceVert * Math.ceil(bottom / mDistanceVert);
107817+
// Step 5: draw lines and texts
107818+
const numbers = [...this.numbers.children];
107819+
for (const number of numbers) {
107820+
number.removeFromParent();
107821+
}
107822+
this.numbers.children = [];
107823+
const mPoints = [];
107824+
for (let i = 0; i < mainGridCountHor; i++) {
107825+
const offset = mTrueLeft + i * mDistanceHor;
107826+
mPoints.push(offset, top, 0, offset, bottom, 0);
107827+
const sign = this.newNumber(offset / this.scaleX);
107828+
const textOffsetPixels = 12;
107829+
const textOffset = textOffsetPixels * unit3dPixelRel;
107830+
sign.position.set(offset, bottom + textOffset, 0);
107831+
}
107832+
for (let i = 0; i < mainGridCountVert; i++) {
107833+
const offset = mTrueBottom + i * mDistanceVert;
107834+
mPoints.push(left, offset, 0, right, offset, 0);
107835+
const sign = this.newNumber(offset / this.scaleY);
107836+
let textOffsetPixels = 12;
107837+
if (sign.element.textContent) {
107838+
textOffsetPixels += 4 * sign.element.textContent.length;
107839+
}
107840+
const textOffset = textOffsetPixels * unit3dPixelRel;
107841+
sign.position.set(left + textOffset, offset, 0);
107842+
}
107843+
const sPoints = [];
107844+
for (let i = 0; i < secondaryGridCountHor; i++) {
107845+
const offset = sTrueLeft + i * sDistanceHor;
107846+
sPoints.push(offset, top, 0, offset, bottom, 0);
107847+
}
107848+
for (let i = 0; i < secondaryGridCountVert; i++) {
107849+
const offset = sTrueBottom + i * sDistanceVert;
107850+
sPoints.push(left, offset, 0, right, offset, 0);
107851+
}
107852+
const mIndices = [];
107853+
const sIndices = [];
107854+
this.fillIndices(mPoints, mIndices);
107855+
this.fillIndices(sPoints, sIndices);
107856+
const mBuffer = new THREE$1.BufferAttribute(new Float32Array(mPoints), 3);
107857+
const sBuffer = new THREE$1.BufferAttribute(new Float32Array(sPoints), 3);
107858+
const { main, secondary } = this.grids;
107859+
main.geometry.setAttribute("position", mBuffer);
107860+
main.geometry.setIndex(mIndices);
107861+
secondary.geometry.setAttribute("position", sBuffer);
107862+
secondary.geometry.setIndex(sIndices);
107863+
}
107864+
fillIndices(points, indices) {
107865+
for (let i = 0; i < points.length / 2 - 1; i += 2) {
107866+
indices.push(i, i + 1);
107867+
}
107868+
}
107869+
newNumber(offset) {
107870+
const text = document.createElement("div");
107871+
text.textContent = `${offset}`;
107872+
if (text.textContent.length > 6) {
107873+
text.textContent = text.textContent.slice(0, 6);
107874+
}
107875+
text.style.height = "24px";
107876+
text.style.fontSize = "12px";
107877+
const sign = new CSS2DObject(text);
107878+
this.numbers.add(sign);
107879+
return sign;
107880+
}
107881+
newGrid(color, renderOrder) {
107882+
const geometry = new THREE$1.BufferGeometry();
107883+
const material = new THREE$1.LineBasicMaterial({ color });
107884+
const grid = new THREE$1.LineSegments(geometry, material);
107885+
grid.frustumCulled = false;
107886+
grid.renderOrder = renderOrder;
107887+
return grid;
107888+
}
107889+
isGridReady() {
107890+
const nums = this._camera.projectionMatrix.elements;
107891+
for (let i = 0; i < nums.length; i++) {
107892+
const num = nums[i];
107893+
if (Number.isNaN(num)) {
107894+
return false;
107895+
}
107896+
}
107897+
return true;
107898+
}
107899+
}
107900+
107737107901
// TODO: Make a scene manager as a Tool (so that it as an UUID)
107738107902
/**
107739107903
* A simple floating 2D scene that you can use to easily draw 2D graphics
107740107904
* with all the power of Three.js.
107741107905
*/
107742107906
class Simple2DScene extends Component {
107907+
get scaleX() {
107908+
return this._scaleX;
107909+
}
107910+
set scaleX(value) {
107911+
this._scaleX = value;
107912+
this._root.scale.x = value;
107913+
this.grid.scaleX = value;
107914+
this.grid.regenerate();
107915+
}
107916+
get scaleY() {
107917+
return this._scaleY;
107918+
}
107919+
set scaleY(value) {
107920+
this._scaleY = value;
107921+
this._root.scale.y = value;
107922+
this.grid.scaleY = value;
107923+
this.grid.regenerate();
107924+
}
107743107925
constructor(components, postproduction = false) {
107744107926
super(components);
107745107927
/** {@link Updateable.onAfterUpdate} */
@@ -107752,6 +107934,9 @@ class Simple2DScene extends Component {
107752107934
this.enabled = true;
107753107935
/** {@link UI.uiElement} */
107754107936
this.uiElement = new UIElement();
107937+
this._scaleX = 1;
107938+
this._scaleY = 1;
107939+
this._root = new THREE$1.Group();
107755107940
this._size = new THREE$1.Vector2();
107756107941
this._frustumSize = 50;
107757107942
/** {@link Resizeable.resize} */
@@ -107769,55 +107954,49 @@ class Simple2DScene extends Component {
107769107954
if (!components.uiEnabled) {
107770107955
throw new Error("The Simple2DScene component needs to use UI elements (TODO: Decouple from them).");
107771107956
}
107772-
const canvas = new Canvas(components);
107773-
canvas.domElement.classList.remove("absolute");
107774-
this.uiElement.set({ canvas });
107775-
this._scene = new THREE$1.Scene();
107957+
const container = new SimpleUIComponent(components);
107958+
container.domElement.classList.add("relative");
107959+
this.uiElement.set({ container });
107960+
this.scene = new THREE$1.Scene();
107776107961
this._size.set(window.innerWidth, window.innerHeight);
107777107962
const { width, height } = this._size;
107778107963
// Creates the camera (point of view of the user)
107779107964
this.camera = new THREE$1.OrthographicCamera(75, width / height);
107780-
this._scene.add(this.camera);
107965+
this.scene.add(this.camera);
107781107966
this.camera.position.z = 10;
107967+
const domContainer = container.domElement;
107968+
this.scene.add(this._root);
107969+
this.grid = new Infinite2dGrid(this.camera, domContainer);
107970+
const gridObject = this.grid.get();
107971+
this.scene.add(gridObject);
107782107972
if (postproduction) {
107783-
this.renderer = new PostproductionRenderer(this.components, undefined, {
107784-
canvas: canvas.get(),
107785-
});
107973+
this.renderer = new PostproductionRenderer(this.components, domContainer);
107786107974
}
107787107975
else {
107788-
this.renderer = new SimpleRenderer(this.components, undefined, {
107789-
canvas: canvas.get(),
107790-
});
107976+
this.renderer = new SimpleRenderer(this.components, domContainer);
107791107977
}
107792107978
const renderer = this.renderer.get();
107793107979
renderer.localClippingEnabled = false;
107794107980
this.renderer.setupEvents(false);
107795-
this.renderer.overrideScene = this._scene;
107981+
this.renderer.overrideScene = this.scene;
107796107982
this.renderer.overrideCamera = this.camera;
107797107983
this.controls = new OrbitControls(this.camera, renderer.domElement);
107798107984
this.controls.target.set(0, 0, 0);
107799107985
this.controls.enableRotate = false;
107800107986
this.controls.enableZoom = true;
107801-
const parent = this.uiElement.get("canvas").parent;
107802-
if (parent) {
107803-
parent.domElement.classList.remove("p-4");
107804-
parent.domElement.classList.remove("overflow-auto");
107805-
parent.domElement.classList.add("overflow-hidden");
107806-
parent.domElement.classList.add("h-full");
107807-
}
107808-
// Creates the orbit controls (to navigate the scene)
107987+
this.controls.addEventListener("change", () => this.grid.regenerate());
107809107988
}
107810107989
/**
107811107990
* {@link Component.get}
107812107991
* @returns the 2D scene.
107813107992
*/
107814107993
get() {
107815-
return this._scene;
107994+
return this._root;
107816107995
}
107817107996
/** {@link Disposable.dispose} */
107818107997
async dispose() {
107819107998
const disposer = await this.components.tools.get(Disposer);
107820-
for (const child of this._scene.children) {
107999+
for (const child of this.scene.children) {
107821108000
const item = child;
107822108001
if (item instanceof THREE$1.Object3D) {
107823108002
disposer.destroy(item);
@@ -115150,7 +115329,7 @@ class RoadNavigator extends Component {
115150115329
const { scene2d } = this.newFloating2DScene("Floorplan", true);
115151115330
const { postproduction } = scene2d.renderer;
115152115331
postproduction.overrideClippingPlanes = true;
115153-
postproduction.overrideScene = scene2d.get();
115332+
postproduction.overrideScene = scene2d.scene;
115154115333
postproduction.overrideCamera = scene2d.camera;
115155115334
postproduction.enabled = true;
115156115335
scene2d.camera.position.set(0, 20, 0);
@@ -115167,7 +115346,7 @@ class RoadNavigator extends Component {
115167115346
this.components.ui.add(floatingWindow);
115168115347
floatingWindow.title = title;
115169115348
const scene2d = new Simple2DScene(this.components, postproduction);
115170-
const canvasUIElement = scene2d.uiElement.get("canvas");
115349+
const canvasUIElement = scene2d.uiElement.get("container");
115171115350
floatingWindow.addChild(canvasUIElement);
115172115351
const style = floatingWindow.slots.content.domElement.style;
115173115352
style.padding = "0";
@@ -115179,7 +115358,7 @@ class RoadNavigator extends Component {
115179115358
scene2d.setSize(clientHeight, clientWidth);
115180115359
await scene2d.update();
115181115360
});
115182-
const canvas = scene2d.uiElement.get("canvas");
115361+
const canvas = scene2d.uiElement.get("container");
115183115362
canvas.domElement.addEventListener("mousemove", async () => {
115184115363
await scene2d.update();
115185115364
});
@@ -115196,7 +115375,7 @@ class RoadNavigator extends Component {
115196115375
this.components.ui.add(drawer);
115197115376
drawer.alignment = "top";
115198115377
const scene2d = new Simple2DScene(this.components);
115199-
const canvasUIElement = scene2d.uiElement.get("canvas");
115378+
const canvasUIElement = scene2d.uiElement.get("container");
115200115379
drawer.addChild(canvasUIElement);
115201115380
const { clientHeight, clientWidth } = drawer.domElement;
115202115381
const windowStyle = drawer.slots.content.domElement.style;

resources/styles.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,10 +1081,6 @@ html {
10811081
overflow: auto;
10821082
}
10831083

1084-
.overflow-hidden{
1085-
overflow: hidden;
1086-
}
1087-
10881084
.overflow-visible{
10891085
overflow: visible;
10901086
}

src/civil/RoadNavigator/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export class RoadNavigator extends Component<any> {
1919
}
2020

2121
get() {
22-
return undefined as any;
2322
}
2423
}
2524

0 commit comments

Comments
 (0)