@inweb/viewer-visualize
Version:
JavaScript library for rendering CAD and BIM files in a browser using VisualizeJS
1,235 lines (1,227 loc) • 193 kB
JavaScript
///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
// This application incorporates Open Design Alliance software pursuant to a
// license agreement with Open Design Alliance.
// Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
// All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////
import { CANVAS_EVENTS, draggersRegistry, commandsRegistry, componentsRegistry, Loader, loadersRegistry, Options, Info } from '@inweb/viewer-core';
export * from '@inweb/viewer-core';
import { EventEmitter2 } from '@inweb/eventemitter2';
import { Markup } from '@inweb/markup';
export * from '@inweb/markup';
class OdaGeAction {
constructor(module) {
this.setViewParams = (params) => {
var _a;
const extView = this.m_module.getViewer().getActiveTvExtendedView();
extView.setView(params.position, params.target, params.upVector, params.viewFieldWidth, params.viewFieldHeight, params.perspective);
(_a = extView.delete) === null || _a === void 0 ? void 0 : _a.call(extView);
};
this.getViewParams = () => {
var _a;
const view = this.m_module.getViewer().activeView;
const obj = {
position: view.viewPosition,
target: view.viewTarget,
upVector: view.upVector,
viewFieldWidth: view.viewFieldWidth,
viewFieldHeight: view.viewFieldHeight,
perspective: view.perspective,
};
(_a = view.delete) === null || _a === void 0 ? void 0 : _a.call(view);
return obj;
};
this.m_module = module;
}
getViewer() {
return this.m_module.getViewer();
}
getModel() {
return this.getViewer().getMarkupModel();
}
copyPoint(point) {
const p = new this.m_module.Point3d();
p.set(point.x, point.y, point.z);
return p;
}
createVector3d() {
return new this.m_module.Vector3d();
}
createPoint3d() {
return new this.m_module.Point3d();
}
createMatrix3d() {
return new this.m_module.Matrix3d();
}
createPlane() {
return new this.m_module.OdTvPlane();
}
toVector(geVector) {
return this.m_module.Vector3d.createFromArray(geVector);
}
toGeVector(v) {
return [v.x, v.y, v.z];
}
toGePoint(point) {
return [point.x, point.y, point.z];
}
toPoint(gePoint) {
return this.m_module.Point3d.createFromArray(gePoint);
}
screenToWorld(x, y) {
return this.toPoint(this.m_module.getViewer().screenToWorld(x, y));
}
toDoubleArray(points) {
const p = [];
for (let i = 0; i < points.length; i++) {
p.push(points[i].x);
p.push(points[i].y);
p.push(points[i].z);
}
return p;
}
correctCameraTarget() {
const params = this.getViewParams();
const ext = this.m_module.getViewer().getActiveExtents();
const { min, max } = ext;
const target = this.toPoint(params.target);
const contains = target.x >= min.x &&
target.y >= min.y &&
target.z >= min.z &&
target.x <= max.x &&
target.y <= max.y &&
target.z <= max.z;
if (!contains) {
params.target = ext.center();
this.setViewParams(params);
}
}
}
const CLICK_DELTA = 5;
const INTERACTIVITY_FPS = 24;
class OdBaseDragger extends OdaGeAction {
constructor(subject) {
super(subject.visualizeJs);
this.beginInteractivity = () => {
const viewer = this.getViewer();
const view = viewer.activeView;
if (view["beginInteractivity"]) {
view.beginInteractivity(INTERACTIVITY_FPS);
this.subject.update();
}
view.delete();
};
this.endInteractivity = () => {
const viewer = this.getViewer();
const view = viewer.activeView;
if (view["endInteractivity"]) {
view.endInteractivity();
const device = this.getViewer().getActiveDevice();
const canvas = this.m_module.canvas;
device.invalidate([0, 0, canvas.width, canvas.height]);
device.delete();
this.subject.update();
}
view.delete();
};
this.subject = subject;
this.needInputText = false;
this.mouseDownPosition = { x: 0, y: 0 };
this.autoSelect = false;
this.onmessage = (event) => this.subject.emitEvent(event);
this.canvasEvents = CANVAS_EVENTS;
}
initialize() {
this.canvasEvents = this.canvasEvents.filter((x) => typeof this[x] === "function");
this.canvasEvents.forEach((x) => (this[x] = this[x].bind(this)));
this.canvasEvents.forEach((x) => this.subject.on(x, this[x]));
this.getViewer().setEnableAutoSelect(!!this.autoSelect);
}
dispose() {
this.canvasEvents.forEach((x) => this.subject.off(x, this[x]));
}
relativeCoords(event) {
return { x: event.offsetX * window.devicePixelRatio, y: event.offsetY * window.devicePixelRatio };
}
pointerdown(ev) {
if (!ev.isPrimary || OdBaseDragger.isGestureActive) {
return;
}
ev.target.setPointerCapture(ev.pointerId);
const relCoord = this.relativeCoords(ev);
this.isDragging = true;
this.mouseDownPosition = { x: relCoord.x, y: relCoord.y };
this.start(relCoord.x, relCoord.y, ev.clientX, ev.clientY);
this.subject.update();
}
pointerup(ev) {
if (OdBaseDragger.needSkipPointerUp) {
return;
}
if (!ev.isPrimary) {
return;
}
ev.target.releasePointerCapture(ev.pointerId);
const relCoord = this.relativeCoords(ev);
this.end(relCoord.x, relCoord.y, ev.clientX, ev.clientY);
this.isDragging = false;
this.subject.update();
}
pointercancel(ev) {
if (!ev.isPrimary) {
return;
}
this.m_module.canvas.dispatchEvent(new PointerEvent("pointerup", ev));
}
pointermove(ev) {
if (!ev.isPrimary || OdBaseDragger.isGestureActive) {
return;
}
const relCoord = this.relativeCoords(ev);
this.drag(relCoord.x, relCoord.y, ev.clientX, ev.clientY);
if (this.isDragging) {
this.subject.update();
}
}
click(ev) {
const viewer = this.getViewer();
const relCoord = this.relativeCoords(ev);
const x = relCoord.x;
const y = relCoord.y;
const isNotDragging = Math.abs(x - this.mouseDownPosition.x) < CLICK_DELTA && Math.abs(y - this.mouseDownPosition.y) < CLICK_DELTA;
if (viewer && viewer.getEnableAutoSelect() && isNotDragging) {
viewer.unselect();
viewer.select(x, y, x, y);
this.subject.update();
const selectionSet = viewer.getSelected();
this.onmessage({ type: "select", data: selectionSet, handles: this.subject.getSelected() });
this.onmessage({ type: "select2", data: selectionSet, handles: this.subject.getSelected2() });
}
}
dblclick(ev) {
const viewer = this.getViewer();
const relCoord = this.relativeCoords(ev);
const x = relCoord.x;
const y = relCoord.y;
const device = viewer.getActiveDevice();
const clickView = device.viewAt([x, y]);
if (clickView && !clickView.active) {
viewer.activeView = clickView;
clickView.delete();
this.subject.update();
}
else {
if (viewer && viewer.getEnableAutoSelect()) {
const pSelected = viewer.getSelected();
if (!pSelected.isNull() && pSelected.numItems() !== 0) {
const itr = pSelected.getIterator();
const entity = itr.getEntity();
viewer.zoomToEntity(entity);
this.onmessage({ type: "zoomtoentity", data: entity });
this.subject.update();
this.deleteAll([itr, entity]);
}
}
}
device.delete();
}
start(x, y, absoluteX = 0, absoluteY = 0) { }
drag(x, y, absoluteX = 0, absoluteY = 0) { }
end(x, y, absoluteX = 0, absoluteY = 0) { }
getActiveMarkupEntity(entityName) {
return this.subject.addMarkupEntity(entityName);
}
syncOverlayView() {
return this.subject.syncOverlay();
}
deleteAll(objects) {
var _a;
for (const obj of objects) {
(_a = obj === null || obj === void 0 ? void 0 : obj.delete) === null || _a === void 0 ? void 0 : _a.call(obj);
}
}
updatePreview() { }
static set isGestureActive(value) {
if (OdBaseDragger._isGestureActive === value) {
return;
}
OdBaseDragger._isGestureActive = value;
if (OdBaseDragger._isGestureActive) {
OdBaseDragger.needSkipPointerUp = true;
}
}
static get isGestureActive() {
return OdBaseDragger._isGestureActive;
}
static get needSkipPointerUp() {
if (OdBaseDragger._needSkipPointerUp) {
OdBaseDragger.needSkipPointerUp = false;
return true;
}
return false;
}
static set needSkipPointerUp(value) {
OdBaseDragger._needSkipPointerUp = value;
}
}
OdBaseDragger._isGestureActive = false;
OdBaseDragger._needSkipPointerUp = false;
function createHtmlElementIfNeed(element, targetElement, dataTestId) {
if (!element) {
element = document.createElement("div");
element.setAttribute("data-testid", dataTestId);
targetElement.appendChild(element);
}
return element;
}
function destroyHtmlElement(element, targetElement) {
if (element) {
targetElement.removeChild(element);
}
return null;
}
function worldToScreen(gePoint, moduleInstance, viewer) {
const worldPoint = moduleInstance.Point3d.createFromArray(gePoint);
const avp = viewer.activeView;
const mtx = avp.worldToDeviceMatrix;
const devicePoint = worldPoint.transformBy(mtx);
const res = { x: devicePoint.x / window.devicePixelRatio, y: devicePoint.y / window.devicePixelRatio };
mtx.delete();
worldPoint.delete();
devicePoint.delete();
avp.delete();
return res;
}
function getDistance(gePoint1, gePoint2, moduleInstance) {
const tvPoint1 = moduleInstance.Point3d.createFromArray(gePoint1);
const tvPoint2 = moduleInstance.Point3d.createFromArray(gePoint2);
const distance = tvPoint1.distanceTo(tvPoint2).toFixed(2);
tvPoint1.delete();
tvPoint2.delete();
return distance;
}
function normalizeFloat(value) {
return value < 0 ? Math.ceil(value) : Math.floor(value);
}
const lineSegmentsIntersect = (p1, p2, p3, p4) => {
const a_dx = p2.x - p1.x;
const a_dy = p2.y - p1.y;
const b_dx = p4.x - p3.x;
const b_dy = p4.y - p3.y;
const s = (-a_dy * (p1.x - p3.x) + a_dx * (p1.y - p3.y)) / (-b_dx * a_dy + a_dx * b_dy);
const t = (+b_dx * (p1.y - p3.y) - b_dy * (p1.x - p3.x)) / (-b_dx * a_dy + a_dx * b_dy);
return s >= 0 && s <= 1 && t >= 0 && t <= 1
? {
x: normalizeFloat(p1.x + t * a_dx),
y: normalizeFloat(p1.y + t * a_dy),
}
: false;
};
function checkSegmentsIntersect(p1, p2, p3, p4, res) {
const r = lineSegmentsIntersect(p1, p2, p3, p4);
if (r) {
res.push(r);
}
}
function isInsideRect(p, width, height) {
return p.x <= width && p.x >= 0 && p.y <= height && p.y >= 0;
}
function getDataForDrawLineWithFixed(p1, p2, width, height) {
const pLU = { x: 0, y: 0 };
const pRU = { x: width, y: 0 };
const pLB = { x: 0, y: height };
const pRB = { x: width, y: height };
const intersects = [];
checkSegmentsIntersect(p1, p2, pLU, pRU, intersects);
checkSegmentsIntersect(p1, p2, pLU, pLB, intersects);
checkSegmentsIntersect(p1, p2, pLB, pRB, intersects);
checkSegmentsIntersect(p1, p2, pRB, pRU, intersects);
let fixedP1 = null;
let fixedP2 = null;
if (intersects.length === 0) {
fixedP1 = p1;
fixedP2 = p2;
}
else if (intersects.length === 1) {
if (isInsideRect(p1, width, height)) {
fixedP1 = p1;
fixedP2 = intersects[0];
}
else {
fixedP1 = intersects[0];
fixedP2 = p2;
}
}
else {
fixedP1 = intersects[0];
fixedP2 = intersects[1];
}
const dx = fixedP2.x - fixedP1.x;
const dy = fixedP2.y - fixedP1.y;
let angle = (180 * Math.atan(dy / dx)) / Math.PI;
if (dx < 0) {
angle -= 180;
}
const size = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
return { angle, width: size, p1: fixedP1, p2: fixedP2 };
}
function onSetCallback(element, cb) {
if (element) {
element.onclick = cb ? () => cb() : () => { };
}
}
function onSetSelectivity(element, enable) {
element.style.pointerEvents = enable ? "auto" : "none";
}
class MeasureLineItem {
constructor(targetElement, viewer, moduleInstance) {
this.htmlElemStartPoint = null;
this.htmlElemEndPoint = null;
this.htmlElemLine = null;
this.htmlElemTitle = null;
this.startPoint = null;
this.endPoint = null;
this.scale = 1.0;
this.unit = "";
this.precision = 2;
this.size = 10.0;
this.lineThickness = 2;
this.style = {
border: "2px solid #FFFFFF",
background: "#009bff",
color: "white",
boxShadow: "0 0 10px rgba(0,0,0,0.5)",
};
this.htmlElemStartPoint = createHtmlElementIfNeed(this.htmlElemStartPoint, targetElement, "ruler-start");
this.htmlElemEndPoint = createHtmlElementIfNeed(this.htmlElemEndPoint, targetElement, "ruler-end");
this.htmlElemLine = createHtmlElementIfNeed(this.htmlElemLine, targetElement, "ruler-line");
this.htmlElemTitle = createHtmlElementIfNeed(this.htmlElemTitle, targetElement, "ruler-value");
this.viewer = viewer;
this.moduleInstance = moduleInstance;
this.targetElement = targetElement;
this.isFinishDraw = false;
}
drawMeasureLine() {
const pointSize = this.size;
const rect = this.moduleInstance.canvas.getBoundingClientRect();
if (this.startPoint) {
this.htmlElemStartPoint = createHtmlElementIfNeed(this.htmlElemStartPoint, this.targetElement, "ruler-start");
const pScreenStart = worldToScreen(this.startPoint, this.moduleInstance, this.viewer);
if (isInsideRect(pScreenStart, rect.width, rect.height)) {
this.htmlElemStartPoint.style.display = "block";
this.htmlElemStartPoint.style.cursor = "pointer";
this.htmlElemStartPoint.style.position = "absolute";
this.htmlElemStartPoint.style.top = `${pScreenStart.y - pointSize / 2}px`;
this.htmlElemStartPoint.style.left = `${pScreenStart.x - pointSize / 2}px`;
this.htmlElemStartPoint.style.borderRadius = `${pointSize}px`;
this.htmlElemStartPoint.style.border = this.style.border;
this.htmlElemStartPoint.style.background = this.style.background;
this.htmlElemStartPoint.style.zIndex = "2";
this.htmlElemStartPoint.style.width = `${pointSize}px`;
this.htmlElemStartPoint.style.height = `${pointSize}px`;
this.htmlElemStartPoint.style.boxShadow = this.style.boxShadow;
}
else {
this.htmlElemStartPoint.style.display = "none";
}
}
if (this.endPoint && this.isFinishDraw) {
this.htmlElemEndPoint = createHtmlElementIfNeed(this.htmlElemEndPoint, this.targetElement, "ruler-end");
const pScreenEnd = worldToScreen(this.endPoint, this.moduleInstance, this.viewer);
if (isInsideRect(pScreenEnd, rect.width, rect.height)) {
this.htmlElemEndPoint.style.display = "block";
this.htmlElemEndPoint.style.cursor = "pointer";
this.htmlElemEndPoint.style.position = "absolute";
this.htmlElemEndPoint.style.top = `${pScreenEnd.y - pointSize / 2}px`;
this.htmlElemEndPoint.style.left = `${pScreenEnd.x - pointSize / 2}px`;
this.htmlElemEndPoint.style.borderRadius = `${pointSize}px`;
this.htmlElemEndPoint.style.border = this.style.border;
this.htmlElemEndPoint.style.background = this.style.background;
this.htmlElemEndPoint.style.zIndex = "2";
this.htmlElemEndPoint.style.width = `${pointSize}px`;
this.htmlElemEndPoint.style.height = `${pointSize}px`;
this.htmlElemEndPoint.style.boxShadow = this.style.boxShadow;
}
else {
this.htmlElemEndPoint.style.display = "none";
}
}
if (this.endPoint && this.startPoint) {
const point1 = worldToScreen(this.startPoint, this.moduleInstance, this.viewer);
const point2 = worldToScreen(this.endPoint, this.moduleInstance, this.viewer);
const { p1, p2, angle, width } = getDataForDrawLineWithFixed(point1, point2, rect.width, rect.height);
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const height = this.lineThickness;
if (isInsideRect(p1, rect.width, rect.height) && isInsideRect(p2, rect.width, rect.height)) {
this.htmlElemLine = createHtmlElementIfNeed(this.htmlElemLine, this.targetElement, "ruler-line");
this.htmlElemLine.style.display = "block";
this.htmlElemLine.style.cursor = "pointer";
this.htmlElemLine.style.position = "absolute";
this.htmlElemLine.style.top = `${p1.y}px`;
this.htmlElemLine.style.left = `${p1.x}px`;
this.htmlElemLine.style.width = `${width}px`;
this.htmlElemLine.style.transform = `rotate(${angle}deg)`;
this.htmlElemLine.style.transformOrigin = `0px ${height / 2}px`;
this.htmlElemLine.style.boxShadow = this.style.boxShadow;
this.htmlElemLine.style.border = "none";
this.htmlElemLine.style.background = this.style.background;
this.htmlElemLine.style.zIndex = "1";
this.htmlElemLine.style.height = `${height}px`;
const distance = this.getDistance();
const pX = p1.x + dx / 2;
const pY = p1.y + dy / 2;
this.htmlElemTitle = createHtmlElementIfNeed(this.htmlElemTitle, this.targetElement, "ruler-value");
this.htmlElemTitle.style.display = "block";
this.htmlElemTitle.style.cursor = "pointer";
this.htmlElemTitle.style.font = "10px";
this.htmlElemTitle.style.color = "white";
this.htmlElemTitle.style.position = "Absolute";
this.htmlElemTitle.style.top = `${pY}px`;
this.htmlElemTitle.style.left = `${pX}px`;
this.htmlElemTitle.style.transform = "translate(-50%, -50%)";
this.htmlElemTitle.style.borderRadius = "5px";
this.htmlElemTitle.style.boxShadow = this.style.boxShadow;
this.htmlElemTitle.style.border = "none";
this.htmlElemTitle.style.background = this.style.background;
this.htmlElemTitle.style.zIndex = "3";
this.htmlElemTitle.style.padding = "2px";
this.htmlElemTitle.style.textAlign = "center";
this.htmlElemTitle.innerHTML = this.formatDistance(distance);
}
else {
this.htmlElemLine.style.display = "none";
this.htmlElemTitle.style.display = "none";
}
}
}
getDistance() {
let distance = getDistance(this.startPoint, this.endPoint, this.moduleInstance);
if (Math.abs(this.scale) > 1e-10)
distance /= this.scale;
return distance;
}
calculatePrecision(value) {
const distance = Math.abs(value);
if (distance >= 1000)
return 0;
if (distance >= 10)
return 1;
if (distance >= 0.1)
return 2;
if (distance >= 0.001)
return 3;
return distance > 0 ? Math.floor(-Math.log10(distance)) + 1 : 2;
}
formatDistance(distance) {
let digits;
if (this.precision === "Auto")
digits = this.calculatePrecision(distance);
else if (Number.isFinite(this.precision))
digits = this.precision;
else
digits = parseFloat(this.precision);
if (!Number.isFinite(digits))
digits = 2;
else if (digits < 0)
digits = 0;
else if (digits > 10)
digits = 10;
let result = distance.toFixed(digits);
if (this.precision === "Auto")
result = result.replace(/\.0+$/, "").replace(/\.$/, "");
if (+result !== distance)
result = "~ " + result;
return `${result} ${this.unit}`;
}
setStartPoint(gePoint) {
this.startPoint = gePoint;
this.drawMeasureLine();
}
setEndPoint(gePoint, isFinish) {
this.isFinishDraw = isFinish === undefined ? true : isFinish;
this.endPoint = gePoint;
this.drawMeasureLine();
}
update() {
this.drawMeasureLine();
}
setSize(size) {
this.size = size;
this.drawMeasureLine();
}
clear() {
this.endPoint = null;
this.startPoint = null;
this.htmlElemStartPoint = destroyHtmlElement(this.htmlElemStartPoint, this.targetElement);
this.htmlElemEndPoint = destroyHtmlElement(this.htmlElemEndPoint, this.targetElement);
this.htmlElemLine = destroyHtmlElement(this.htmlElemLine, this.targetElement);
this.htmlElemTitle = destroyHtmlElement(this.htmlElemTitle, this.targetElement);
}
setUnit(unit) {
this.unit = unit;
this.drawMeasureLine();
}
setConversionFactor(scale) {
this.scale = scale;
this.drawMeasureLine();
}
setPrecision(precision) {
this.precision = precision;
this.drawMeasureLine();
}
setStyle(style) {
this.style = style;
this.drawMeasureLine();
}
setSelectionReactor(reactor) {
onSetCallback(this.htmlElemStartPoint, reactor ? reactor.onStartPoint : null);
onSetCallback(this.htmlElemEndPoint, reactor ? reactor.onEndPoint : null);
onSetCallback(this.htmlElemTitle, reactor ? reactor.onTitle : null);
}
setSelectability(enable) {
onSetSelectivity(this.htmlElemStartPoint, enable);
onSetSelectivity(this.htmlElemEndPoint, enable);
onSetSelectivity(this.htmlElemLine, enable);
onSetSelectivity(this.htmlElemTitle, enable);
}
}
function renameUnit(table, unit) {
return table[unit] || unit;
}
class MeasureLineDragger extends OdBaseDragger {
constructor(subject) {
var _a, _b;
super(subject);
this.lineThickness = 2;
this.press = false;
this.gripingRadius = 5.0;
this.firstPoint = null;
this.secondPoint = null;
this.rulerUnitTable = {
Millimeters: "mm",
Centimeters: "cm",
Meters: "m",
Feet: "ft",
Inches: "in",
Yards: "yd",
Kilometers: "km",
Miles: "mi",
Micrometers: "µm",
Mils: "mil",
MicroInches: "µin",
Default: "unit",
};
this.rulerUnit = (_a = subject.options.rulerUnit) !== null && _a !== void 0 ? _a : "Default";
this.rulerPrecision = (_b = subject.options.rulerPrecision) !== null && _b !== void 0 ? _b : "Default";
this.items = [];
this.canvasEvents.push("resize", "optionsChange");
}
initialize() {
super.initialize();
this.m_overlayElement = document.createElement("div");
this.m_overlayElement.style.background = "rgba(0,0,0,0)";
this.m_overlayElement.style.position = "fixed";
this.m_overlayElement.style.zIndex = "1";
this.m_overlayElement.style.pointerEvents = "none";
document.body.appendChild(this.m_overlayElement);
this.subject.addEventListener("optionschange", this.optionsChange);
this.resize();
}
dispose() {
super.dispose();
this.m_overlayElement.remove();
this.subject.removeEventListener("optionschange", this.optionsChange);
}
updatePreview() {
this.items.forEach((item) => item.update());
}
resize() {
const rect = this.m_module.canvas.getBoundingClientRect();
this.m_overlayElement.style.top = `${rect.top}px`;
this.m_overlayElement.style.left = `${rect.left}px`;
this.m_overlayElement.style.width = `${rect.width}px`;
this.m_overlayElement.style.height = `${rect.height}px`;
}
getSnapPointRadius() {
const view = this.getViewer().activeView;
const corners = view.viewDcCorners();
const pt1 = corners.lowerLeft;
const pt2 = corners.upperRight;
pt2[0] -= pt1[0];
pt2[1] -= pt1[1];
return Math.min(pt2[0], pt2[1]) / 120;
}
start(x, y) {
this.createNewMeasureIfNeed();
const point = this.getViewer().getSnapPoint(x, y, this.gripingRadius);
if (point) {
this.firstPoint = point;
this.previewMeasureLine.setStartPoint(this.firstPoint);
}
}
drag(x, y) {
this.createNewMeasureIfNeed();
const point = this.getViewer().getSnapPoint(x, y, this.gripingRadius);
if (this.isDragging) {
if (point) {
if (this.firstPoint) {
this.secondPoint = point;
this.previewMeasureLine.setStartPoint(this.firstPoint);
this.previewMeasureLine.setEndPoint(this.secondPoint, true);
}
else {
this.firstPoint = point;
this.previewMeasureLine.setStartPoint(this.firstPoint);
}
}
else {
this.secondPoint = null;
this.previewMeasureLine.clear();
this.previewMeasureLine.setStartPoint(this.firstPoint);
this.previewMeasureLine.setEndPoint(this.getViewer().screenToWorld(x, y), false);
}
}
else {
if (point) {
this.previewMeasureLine.setStartPoint(point);
}
else {
this.previewMeasureLine.clear();
}
}
}
end() {
if (this.firstPoint && this.secondPoint) {
const newLineMeasure = this.createMeasureLine();
newLineMeasure.setStartPoint(this.firstPoint);
newLineMeasure.setEndPoint(this.secondPoint, true);
}
this.firstPoint = null;
this.secondPoint = null;
this.previewMeasureLine.clear();
}
createNewMeasureIfNeed() {
if (!this.previewMeasureLine) {
this.previewMeasureLine = this.createMeasureLine();
}
}
createMeasureLine() {
const viewer = this.m_module.getViewer();
const item = new MeasureLineItem(this.m_overlayElement, viewer, this.m_module);
item.lineThickness = this.lineThickness || item.lineThickness;
const isDefaultUnit = this.rulerUnit === "Default";
const isDefaultPrecision = this.rulerPrecision === "Default";
item.setUnit(renameUnit(this.rulerUnitTable, isDefaultUnit ? viewer.getUnit() : this.rulerUnit));
if (!isDefaultUnit) {
const fromUnit = this.getKUnitByName(viewer.getUnit());
const toUnit = this.getKUnitByName(this.subject.options.rulerUnit);
const multiplier = viewer.getUnitsConversionCoef(fromUnit, toUnit);
this.conversionFactor = 1 / multiplier;
item.setConversionFactor(this.conversionFactor);
}
else {
item.setConversionFactor(1.0);
}
if (!isDefaultPrecision) {
item.setPrecision(this.rulerPrecision);
}
else {
item.setPrecision(2);
}
this.items.push(item);
return item;
}
optionsChange(event) {
var _a, _b;
const options = event.data;
const toUnitName = (_a = options.rulerUnit) !== null && _a !== void 0 ? _a : "Default";
const toPrecision = (_b = options.rulerPrecision) !== null && _b !== void 0 ? _b : "Default";
const unitChanged = this.rulerUnit !== toUnitName;
const precisionChanged = this.rulerPrecision !== toPrecision;
if (!unitChanged && !precisionChanged)
return;
this.rulerUnit = toUnitName;
this.rulerPrecision = toPrecision;
const drawingUnit = this.m_module.getViewer().getUnit();
const eToUnit = this.getKUnitByName(toUnitName);
const eFromUnit = this.getKUnitByName(drawingUnit);
this.items.forEach((item) => {
if (unitChanged) {
if (toUnitName === "Default") {
item.setUnit(renameUnit(this.rulerUnitTable, drawingUnit));
item.setConversionFactor(1.0);
}
else {
item.setUnit(renameUnit(this.rulerUnitTable, toUnitName));
const multiplier = this.m_module.getViewer().getUnitsConversionCoef(eFromUnit, eToUnit);
this.conversionFactor = 1 / multiplier;
item.setConversionFactor(this.conversionFactor);
}
}
if (precisionChanged) {
if (toPrecision === "Default") {
item.setPrecision(2);
}
else {
item.setPrecision(toPrecision);
}
}
});
}
getKUnitByName(unitName) {
let eUnit = this.m_module.Units.kUserDefined;
switch (unitName) {
case "Millimeters":
eUnit = this.m_module.Units.kMillimeters;
break;
case "Centimeters":
eUnit = this.m_module.Units.kCentimeters;
break;
case "Meters":
eUnit = this.m_module.Units.kMeters;
break;
case "Feet":
eUnit = this.m_module.Units.kFeet;
break;
case "Inches":
eUnit = this.m_module.Units.kInches;
break;
case "Yards":
eUnit = this.m_module.Units.kYards;
break;
case "Kilometers":
eUnit = this.m_module.Units.kKilometers;
break;
case "Miles":
eUnit = this.m_module.Units.kMiles;
break;
case "Micrometers":
eUnit = this.m_module.Units.kMicrometers;
break;
case "Mils":
eUnit = this.m_module.Units.kMils;
break;
case "MicroInches":
eUnit = this.m_module.Units.kMicroInches;
break;
}
return eUnit;
}
}
class OdJoyStickDragger {
constructor(global, container, callback, canvasElement) {
this.hasEventListeners = false;
const internalLineWidth = 2;
const internalStrokeColor = "#003300";
const externalLineWidth = 2;
const externalStrokeColor = "#35436E";
this.container = container;
this.container.style.touchAction = "none";
this.canvas = document.createElement("canvas");
this.canvas.id = "odJoyStickCanvas";
this.canvas.width = 200;
this.canvas.height = 200;
this.container.appendChild(this.canvas);
const context = this.canvas.getContext("2d");
let pressed = 0;
const circumference = 2 * Math.PI;
const internalRadius = (this.canvas.width - (this.canvas.width / 2 + 10)) / 2;
const maxMoveStick = internalRadius + 5;
const externalRadius = internalRadius + 30;
const centerX = this.canvas.width / 2;
const centerY = this.canvas.height / 2;
let movedX = centerX;
let movedY = centerY;
this.onMouseDown = () => {
event.preventDefault();
pressed = 1;
};
this.onMouseMove = (event) => {
event.preventDefault();
if (pressed === 1) {
movedX = event.pageX;
movedY = event.pageY;
if (this.canvas.offsetParent && this.canvas.offsetParent.tagName.toUpperCase() === "BODY") {
movedX -= this.canvas.offsetLeft;
movedY -= this.canvas.offsetTop;
}
else if (this.canvas.offsetParent) {
movedX -= this.canvas.offsetParent.offsetLeft;
movedY -= this.canvas.offsetParent.offsetTop;
}
context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.drawExternal();
this.drawInternal();
callback({
x: 100 * ((movedX - centerX) / maxMoveStick),
y: 100 * ((movedY - centerY) / maxMoveStick) * -1,
global,
});
}
};
this.onMouseUp = () => {
event.preventDefault();
pressed = 0;
movedX = centerX;
movedY = centerY;
context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.drawExternal();
this.drawInternal();
callback({
x: 100 * ((movedX - centerX) / maxMoveStick),
y: 100 * ((movedY - centerY) / maxMoveStick) * -1,
global,
});
};
this.drawExternal = () => {
context.beginPath();
context.arc(centerX, centerY, externalRadius, 0, circumference, false);
context.lineWidth = externalLineWidth;
context.strokeStyle = externalStrokeColor;
context.globalAlpha = 0.5;
context.stroke();
};
this.drawInternal = () => {
context.beginPath();
if (movedX < internalRadius) {
movedX = maxMoveStick;
}
if (movedX + internalRadius > this.canvas.width) {
movedX = this.canvas.width - maxMoveStick;
}
if (movedY < internalRadius) {
movedY = maxMoveStick;
}
if (movedY + internalRadius > this.canvas.height) {
movedY = this.canvas.height - maxMoveStick;
}
context.arc(movedX, movedY, internalRadius, 0, circumference, false);
context.fillStyle = externalStrokeColor;
context.lineWidth = internalLineWidth;
context.strokeStyle = internalStrokeColor;
context.globalAlpha = 0.5;
context.fill();
context.stroke();
};
const addEventListeners = () => {
if (!this.hasEventListeners) {
this.canvas.addEventListener("pointerdown", this.onMouseDown, false);
document.addEventListener("pointermove", this.onMouseMove, false);
document.addEventListener("pointerup", this.onMouseUp, false);
this.hasEventListeners = true;
}
};
const removeEventListeners = () => {
if (this.hasEventListeners) {
this.canvas.removeEventListener("pointerdown", this.onMouseDown, false);
document.removeEventListener("pointermove", this.onMouseMove, false);
document.removeEventListener("pointerup", this.onMouseUp, false);
this.hasEventListeners = false;
}
};
const updateContainerPosition = () => {
if (canvasElement) {
const rect = canvasElement.getBoundingClientRect();
this.container.style.top = `${rect.height - 200}px`;
this.container.style.left = `${rect.left}px`;
this.container.style.width = `200px`;
this.container.style.height = `200px`;
}
};
const updateVisibility = () => {
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const isNarrowScreen = window.innerWidth < 1024;
const shouldShow = isMobile || isNarrowScreen;
if (shouldShow) {
this.container.style.display = "block";
addEventListeners();
}
else {
this.container.style.display = "none";
removeEventListeners();
}
};
this.onResize = () => {
updateVisibility();
setTimeout(updateContainerPosition, 500);
};
updateVisibility();
updateContainerPosition();
window.addEventListener("resize", this.onResize, false);
this.drawExternal();
this.drawInternal();
}
cleanup() {
window.removeEventListener("resize", this.onResize, false);
if (this.hasEventListeners) {
this.canvas.removeEventListener("pointerdown", this.onMouseDown, false);
document.removeEventListener("pointermove", this.onMouseMove, false);
document.removeEventListener("pointerup", this.onMouseUp, false);
this.hasEventListeners = false;
}
this.canvas.remove();
}
}
const FocalLengthConst$1 = 42.0;
const calcFocalLength$1 = (lensLength, fieldWidth, fieldHeight) => {
return (lensLength / FocalLengthConst$1) * Math.sqrt(fieldWidth * fieldWidth + fieldHeight * fieldHeight);
};
class OdaWalkDragger extends OdBaseDragger {
constructor(subject) {
super(subject);
this.viewer = undefined;
this.multiplier = 5;
this.baseSpeed = 1;
this.keyPressMap = new Set();
this.keydown = this.keydown.bind(this);
this.keyup = this.keyup.bind(this);
this.lastFrameTS = 0;
this.lastFrameJoyStickTS = 0;
this.animationId = undefined;
this.processMovement = this.processMovement.bind(this);
this.processJoyStickMovement = this.processJoyStickMovement.bind(this);
this.deltaAngle = Math.PI / 3600;
this.autoSelect = true;
this.isJoyStickMoving = false;
this.addJoyStickDragger(subject.canvas.parentElement);
}
initialize() {
super.initialize();
this.viewer = this.getViewer();
window.addEventListener("keydown", this.keydown, false);
window.addEventListener("keyup", this.keyup, false);
this.oldWCSEnableValue = this.viewer.getEnableWCS();
this.viewer.setEnableWCS(false);
const view = this.viewer.activeView;
const maxDimension = this.getMaxDimension(view);
this.baseSpeed = maxDimension / 30000;
this.subject.emitEvent({ type: "walkstart" });
this.viewParams = this.getViewParams();
this.setViewParams(this.viewParams);
const model = this.viewer.getActiveModel();
this.cameraId = model.appendCamera("Camera0");
this.setupCamera(view);
model.delete();
this.cameraWalker = new this.m_module.OdTvCameraWalker();
this.cameraWalker.setCamera(this.cameraId);
this.subject.update();
this.enableZoomWheelPreviousValue = this.subject.options.enableZoomWheel;
this.subject.options.enableZoomWheel = false;
}
dispose() {
var _a;
this.oldWCSEnableValue =
this.oldWCSEnableValue !== undefined ? this.oldWCSEnableValue : this.subject.options.showWCS;
this.viewer.setEnableWCS(this.oldWCSEnableValue);
super.dispose();
this.keyPressMap.clear();
window.removeEventListener("keydown", this.keydown);
window.removeEventListener("keyup", this.keyup);
if (this.animationId) {
window.cancelAnimationFrame(this.animationId);
this.animationId = undefined;
}
if (this.cameraId) {
const model = this.viewer.getActiveModel();
model.removeEntity(this.cameraId);
model.delete();
(_a = this.cameraWalker) === null || _a === void 0 ? void 0 : _a.delete();
}
if (this.viewParams) {
this.setViewParams(this.viewParams);
const avp = this.viewer.activeView;
avp.delete();
}
this.subject.update(true);
this.subject.options.enableZoomWheel = this.enableZoomWheelPreviousValue;
this.joyStickOverlayElement.remove();
this.joyStickDragger.cleanup();
}
keydown(ev) {
switch (ev.code) {
case "NumpadSubtract":
case "Minus":
if (this.multiplier > 1) {
this.multiplier = this.multiplier - 1;
this.subject.emitEvent({ type: "walkspeedchange", data: this.multiplier });
}
break;
case "NumpadAdd":
case "Equal":
if (this.multiplier < 10) {
this.multiplier = this.multiplier + 1;
this.subject.emitEvent({ type: "walkspeedchange", data: this.multiplier });
}
break;
case "KeyW":
case "KeyA":
case "KeyS":
case "KeyD":
case "KeyQ":
case "KeyE":
this.keyPressMap.add(ev.code);
if (!this.animationId)
this.processMovement(0);
break;
}
}
keyup(ev) {
this.keyPressMap.delete(ev.code);
if (this.keyPressMap.size < 1 && this.animationId) {
window.cancelAnimationFrame(this.animationId);
this.animationId = undefined;
this.lastFrameTS = 0;
}
}
processMovement(timestamp) {
this.animationId = requestAnimationFrame(this.processMovement);
if (this.lastFrameTS !== 0) {
const deltaTS = timestamp - this.lastFrameTS;
if (deltaTS > 0) {
const currentDelta = this.multiplier * deltaTS * this.baseSpeed;
Array.from(this.keyPressMap).forEach((keyCode) => {
switch (keyCode) {
case "KeyW":
this.moveForward(currentDelta);
break;
case "KeyS":
this.moveBackward(currentDelta);
break;
case "KeyA":
this.cameraWalker.moveLeft(currentDelta);
break;
case "KeyD":
this.cameraWalker.moveRight(currentDelta);
break;
case "KeyQ":
this.cameraWalker.moveUp(currentDelta);
break;
case "KeyE":
this.cameraWalker.moveDown(currentDelta);
break;
}
});
this.proceedChangeCamera();
}
}
this.lastFrameTS = timestamp;
}
start(x, y) {
this.dragPosition = { x, y };
}
drag(x, y) {
if (this.cameraId && this.isDragging) {
const dltX = x - this.dragPosition.x;
const dltY = y - this.dragPosition.y;
this.dragPosition = { x, y };
if (dltX !== 0.0)
this.turnLeft(-dltX * this.deltaAngle);
if (dltY !== 0.0)
this.cameraWalker.turnDown(dltY * this.deltaAngle);
this.subject.update();
this.subject.emitEvent({ type: "changecamera" });
}
}
moveForward(currentDelta) {
const { Vector3d } = this.m_module;
const camera = this.cameraWalker.camera().openObjectAsCamera();
const target = Vector3d.createFromArray(camera.target());
const dir = Vector3d.createFromArray(camera.direction());
const up = Vector3d.createFromArray(camera.upVector());
const pos = Vector3d.createFromArray(camera.position());
let move = Vector3d.createFromArray([dir.x, dir.y, 0]);
if (Math.abs(dir.x) > 0.001 && Math.abs(dir.y) > 0.001) {
move.setToProduct(move.normalize(), currentDelta);
}
else {
move = Vector3d.createFromArray([0, currentDelta, 0]);
}
const newPos = pos.add(move);
const newTarget = target.add(move);
camera.setupCamera(newPos.toArray(), newTarget.toArray(), up.toArray());
}
moveBackward(currentDelta) {
this.moveForward(-currentDelta);
}
turnLeft(angle) {
const pCamera = this.cameraWalker.camera().openObjectAsCamera();
const dir = this.toVector(pCamera.direction());
const up = this.toVector(pCamera.upVector());
const pos = pCamera.position();
const rotMatrix = this.createMatrix3d();
const zAxisVector = [0, 0, 1];
rotMatrix.setToRotation(angle, zAxisVector, pos);
dir.transformBy(rotMatrix);
up.transformBy(rotMatrix);
pCamera.setupCameraByDirection(pos, dir.toArray(), up.toArray());
pCamera.delete();
}
setupCamera(view) {
const pCamera = this.cameraId.openObjectAsCamera();
const target = view.viewTarget;
pCamera.setDisplayGlyph(false);
pCamera.setDisplayTarget(false);
pCamera.setAutoAdjust(true);
pCamera.setupCamera(view.viewPosition, target, view.upVector);
pCamera.setNearClip(false, 1.0);
pCamera.setFarClip(false, 0);
pCamera.setViewParameters(view.viewFieldWidth, view.viewFieldHeight, true);
const focalL = calcFocalLength$1(view.lensLength, view.viewFieldWidth, view.viewFieldHeight);
const pTarget = this.toPoint(view.viewTarget);
const viewDir = this.toPoint(view.viewPosition);
const viewDirSub = viewDir.sub(pTarget);
const viewDirVec = viewDirSub.asVector();
const viewDirVecNormal = viewDirVec.normalize();
const geViewDir = this.toGeVector(viewDirVecNormal);
const newGeViewDir = [geViewDir[0] * focalL, geViewDir[1] * focalL, geViewDir[2] * focalL];
const pTarget2 = this.toPoint(view.viewTarget);
const newGeViewDirPt = this.toPoint(newGeViewDir);
const newPos = pTarget2.add(newGeViewDirPt);
pCamera.setupCamera(this.toGePoint(newPos), view.viewTarget, view.upVector);
this.deleteAll([pTarget, viewDir, viewDirSub, viewDirVec, viewDirVecNormal, pTarget2, newGeViewDirPt, newPos]);
pCamera.assignView(view);
pCamera.delete();
}
getMaxDimension(view) {
const [xmax, ymax, zmax] = view.sceneExtents.max();
const [xmin, ymin, zmin] = view.sceneExtents.min();
const volume = [xmax - xmin, ymax - ymin, zmax - zmin];
return Math.max(...volume);
}
addJoyStickDragger(parentElement) {
this.joyStickOverlayElement = document.createElement("div");
this.joyStickOverlayElement.id = "joyStickDiv";
this.joyStickOverlayElement.style.background = "rgba(0,0,0,0)";
this.joyStickOverlayElement.style.position = "fixed";
this.joyStickOverlayElement.style.zIndex = "0";
parentElement.appendChild(this.joyStickOverlayElement);
this.joyStickDragger = new OdJoyStickDragger(this, this.joyStickOverlayElement, (stickData) => {
if (Math.sqrt(stickData.x * stickData.x + stickData.y * stickData.y) > 20) {
this.lastJoyStickCoord = { x: stickData.x, y: stickData.y };
if (!this.animationId && !this.isJoyStickMoving) {
this.isJoyStickMoving = true;
this.processJoyStickMovement(0);
}
}
else {
this.isJoyStickMoving = false;
window.cancelAnimationFrame(this.animationId);
this.animationId = undefined;
this.l