@ae-studio/dxf-viewer
Advanced tools
Comparing version 1.1.1 to 1.1.2
{ | ||
"name": "@ae-studio/dxf-viewer", | ||
"version": "1.1.1", | ||
"version": "1.1.2", | ||
"description": "JavaScript DXF file viewer", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
2197
src/DxfViewer.js
@@ -1,229 +0,236 @@ | ||
import * as three from "three" | ||
import {BatchingKey} from "./BatchingKey" | ||
import {DxfWorker} from "./DxfWorker" | ||
import {MaterialKey} from "./MaterialKey" | ||
import {ColorCode, DxfScene} from "./DxfScene" | ||
import {OrbitControls} from "./OrbitControls" | ||
import {RBTree} from "./RBTree" | ||
import * as three from 'three'; | ||
import { BatchingKey } from './BatchingKey'; | ||
import { DxfWorker } from './DxfWorker'; | ||
import { MaterialKey } from './MaterialKey'; | ||
import { ColorCode, DxfScene } from './DxfScene'; | ||
import { OrbitControls } from './OrbitControls'; | ||
import { RBTree } from './RBTree'; | ||
/** Level in "message" events. */ | ||
const MessageLevel = Object.freeze({ | ||
INFO: "info", | ||
WARN: "warn", | ||
ERROR: "error" | ||
}) | ||
INFO: 'info', | ||
WARN: 'warn', | ||
ERROR: 'error', | ||
}); | ||
/** The representation class for the viewer, based on Three.js WebGL renderer. */ | ||
export class DxfViewer { | ||
/** @param domContainer Container element to create the canvas in. Usually empty div. Should not | ||
* have padding if auto-resize feature is used. | ||
* @param options Some options can be overridden if specified. See DxfViewer.DefaultOptions. | ||
*/ | ||
constructor(domContainer, options = null) { | ||
this.sceneData = null; | ||
this.n = 0; | ||
this.domContainer = domContainer; | ||
this.options = Object.create(DxfViewer.DefaultOptions); | ||
this.objects = new Map(); | ||
if (options) { | ||
Object.assign(this.options, options); | ||
} | ||
options = this.options; | ||
/** @param domContainer Container element to create the canvas in. Usually empty div. Should not | ||
* have padding if auto-resize feature is used. | ||
* @param options Some options can be overridden if specified. See DxfViewer.DefaultOptions. | ||
*/ | ||
constructor(domContainer, options = null) { | ||
this.sceneData = null | ||
this.n = 0; | ||
this.domContainer = domContainer | ||
this.options = Object.create(DxfViewer.DefaultOptions) | ||
this.objects = new Map() | ||
if (options) { | ||
Object.assign(this.options, options) | ||
} | ||
options = this.options | ||
this.clearColor = this.options.clearColor.getHex(); | ||
this.clearColor = this.options.clearColor.getHex() | ||
this.scene = new three.Scene(); | ||
this.scene = new three.Scene() | ||
try { | ||
this.renderer = new three.WebGLRenderer({ | ||
alpha: options.canvasAlpha, | ||
premultipliedAlpha: options.canvasPremultipliedAlpha, | ||
antialias: options.antialias, | ||
depth: false, | ||
preserveDrawingBuffer: options.preserveDrawingBuffer, | ||
}); | ||
try { | ||
this.renderer = new three.WebGLRenderer({ | ||
alpha: options.canvasAlpha, | ||
premultipliedAlpha: options.canvasPremultipliedAlpha, | ||
antialias: options.antialias, | ||
depth: false, | ||
preserveDrawingBuffer: options.preserveDrawingBuffer | ||
}) | ||
globalThis.renderer = this.renderer | ||
} catch (e) { | ||
console.log("Failed to create renderer: " + e) | ||
this.renderer = null | ||
return | ||
} | ||
const renderer = this.renderer | ||
/* Prevent bounding spheres calculations which fails due to non-conventional geometry | ||
* buffers layout. Also do not waste CPU on sorting which we do not need anyway. | ||
*/ | ||
renderer.sortObjects = false | ||
renderer.setPixelRatio(window.devicePixelRatio) | ||
globalThis.renderer = this.renderer; | ||
} catch (e) { | ||
console.log('Failed to create renderer: ' + e); | ||
this.renderer = null; | ||
return; | ||
} | ||
const renderer = this.renderer; | ||
/* Prevent bounding spheres calculations which fails due to non-conventional geometry | ||
* buffers layout. Also do not waste CPU on sorting which we do not need anyway. | ||
*/ | ||
renderer.sortObjects = false; | ||
renderer.setPixelRatio(window.devicePixelRatio); | ||
const camera = this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0.1, 2); | ||
camera.position.z = 1 | ||
camera.position.x = 0 | ||
camera.position.y = 0 | ||
const camera = (this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0.1, 2)); | ||
camera.position.z = 1; | ||
camera.position.x = 0; | ||
camera.position.y = 0; | ||
globalThis.camera = camera | ||
globalThis.scene = this.scene | ||
globalThis.camera = camera; | ||
globalThis.scene = this.scene; | ||
this.simpleColorMaterial = [] | ||
this.simplePointMaterial = [] | ||
for (let i = 0; i < InstanceType.MAX; i++) { | ||
this.simpleColorMaterial[i] = this._CreateSimpleColorMaterial(i) | ||
this.simplePointMaterial[i] = this._CreateSimplePointMaterial(i) | ||
} | ||
this.simpleColorMaterial = []; | ||
this.simplePointMaterial = []; | ||
for (let i = 0; i < InstanceType.MAX; i++) { | ||
this.simpleColorMaterial[i] = this._CreateSimpleColorMaterial(i); | ||
this.simplePointMaterial[i] = this._CreateSimplePointMaterial(i); | ||
} | ||
renderer.setClearColor(options.clearColor, options.clearAlpha) | ||
renderer.setClearColor(options.clearColor, options.clearAlpha); | ||
if (options.autoResize) { | ||
this.canvasWidth = domContainer.clientWidth | ||
this.canvasHeight = domContainer.clientHeight | ||
domContainer.style.position = "relative" | ||
} else { | ||
this.canvasWidth = options.canvasWidth | ||
this.canvasHeight = options.canvasHeight | ||
this.resizeObserver = null | ||
} | ||
renderer.setSize(this.canvasWidth, this.canvasHeight) | ||
if (options.autoResize) { | ||
this.canvasWidth = domContainer.clientWidth; | ||
this.canvasHeight = domContainer.clientHeight; | ||
domContainer.style.position = 'relative'; | ||
} else { | ||
this.canvasWidth = options.canvasWidth; | ||
this.canvasHeight = options.canvasHeight; | ||
this.resizeObserver = null; | ||
} | ||
renderer.setSize(this.canvasWidth, this.canvasHeight); | ||
this.canvas = renderer.domElement | ||
domContainer.style.display = "block" | ||
if (options.autoResize) { | ||
this.canvas.style.position = "absolute" | ||
this.resizeObserver = new ResizeObserver(entries => this._OnResize(entries[0])) | ||
this.resizeObserver.observe(domContainer) | ||
} | ||
domContainer.appendChild(this.canvas) | ||
this.canvas = renderer.domElement; | ||
domContainer.style.display = 'block'; | ||
if (options.autoResize) { | ||
this.canvas.style.position = 'absolute'; | ||
this.resizeObserver = new ResizeObserver((entries) => this._OnResize(entries[0])); | ||
this.resizeObserver.observe(domContainer); | ||
} | ||
domContainer.appendChild(this.canvas); | ||
this.canvas.addEventListener("click", this._OnPointerEvent.bind(this)) | ||
// this.canvas.addEventListener("pointerup", this._OnPointerEvent.bind(this)) | ||
this.canvas.addEventListener('click', this._OnPointerEvent.bind(this)); | ||
// this.canvas.addEventListener("pointerup", this._OnPointerEvent.bind(this)) | ||
this.Render() | ||
this.Render(); | ||
/* Indexed by MaterialKey, value is {key, material}. */ | ||
this.materials = new RBTree((m1, m2) => m1.key.Compare(m2.key)) | ||
/* Indexed by layer name, value is Layer instance. */ | ||
this.layers = new Map() | ||
/* Indexed by block name, value is Block instance. */ | ||
this.blocks = new Map() | ||
/* Indexed by MaterialKey, value is {key, material}. */ | ||
this.materials = new RBTree((m1, m2) => m1.key.Compare(m2.key)); | ||
/* Indexed by layer name, value is Layer instance. */ | ||
this.layers = new Map(); | ||
/* Indexed by block name, value is Block instance. */ | ||
this.blocks = new Map(); | ||
/** Set during data loading. */ | ||
this.worker = null | ||
} | ||
/** Set during data loading. */ | ||
this.worker = null; | ||
} | ||
/** @return {boolean} True if renderer exists. May be false in case when WebGL context is lost | ||
* (e.g. after wake up from sleep). In such case page should be reloaded. | ||
*/ | ||
HasRenderer() { | ||
return Boolean(this.renderer) | ||
} | ||
/** @return {boolean} True if renderer exists. May be false in case when WebGL context is lost | ||
* (e.g. after wake up from sleep). In such case page should be reloaded. | ||
*/ | ||
HasRenderer() { | ||
return Boolean(this.renderer); | ||
} | ||
/** | ||
* @returns {three.WebGLRenderer | null} Returns the created Three.js renderer. | ||
*/ | ||
GetRenderer(){ | ||
return this.renderer; | ||
} | ||
/** | ||
* @returns {three.WebGLRenderer | null} Returns the created Three.js renderer. | ||
*/ | ||
GetRenderer() { | ||
return this.renderer; | ||
} | ||
GetCanvas() { | ||
return this.canvas | ||
} | ||
GetCanvas() { | ||
return this.canvas; | ||
} | ||
GetDxf() { | ||
return this.parsedDxf | ||
} | ||
GetDxf() { | ||
return this.parsedDxf; | ||
} | ||
GetControls() { | ||
return this.controls | ||
} | ||
GetControls() { | ||
return this.controls; | ||
} | ||
SetSize(width, height) { | ||
this._EnsureRenderer() | ||
SetSize(width, height) { | ||
this._EnsureRenderer(); | ||
const hScale = width / this.canvasWidth | ||
const vScale = height / this.canvasHeight | ||
const hScale = width / this.canvasWidth; | ||
const vScale = height / this.canvasHeight; | ||
const cam = this.camera | ||
const centerX = (cam.left + cam.right) / 2 | ||
const centerY = (cam.bottom + cam.top) / 2 | ||
const camWidth = cam.right - cam.left | ||
const camHeight = cam.top - cam.bottom | ||
cam.left = centerX - hScale * camWidth / 2 | ||
cam.right = centerX + hScale * camWidth / 2 | ||
cam.bottom = centerY - vScale * camHeight / 2 | ||
cam.top = centerY + vScale * camHeight / 2 | ||
cam.updateProjectionMatrix() | ||
const cam = this.camera; | ||
const centerX = (cam.left + cam.right) / 2; | ||
const centerY = (cam.bottom + cam.top) / 2; | ||
const camWidth = cam.right - cam.left; | ||
const camHeight = cam.top - cam.bottom; | ||
cam.left = centerX - (hScale * camWidth) / 2; | ||
cam.right = centerX + (hScale * camWidth) / 2; | ||
cam.bottom = centerY - (vScale * camHeight) / 2; | ||
cam.top = centerY + (vScale * camHeight) / 2; | ||
cam.updateProjectionMatrix(); | ||
this.canvasWidth = width | ||
this.canvasHeight = height | ||
this.renderer.setSize(width, height) | ||
if (this.controls) { | ||
this.controls.update() | ||
} | ||
this._Emit("resized", {width, height}) | ||
this._Emit("viewChanged") | ||
this.Render() | ||
this.canvasWidth = width; | ||
this.canvasHeight = height; | ||
this.renderer.setSize(width, height); | ||
if (this.controls) { | ||
this.controls.update(); | ||
} | ||
this._Emit('resized', { width, height }); | ||
this._Emit('viewChanged'); | ||
this.Render(); | ||
} | ||
/** Load DXF into the viewer. Old content is discarded, state is reset. | ||
* @param url {string} DXF file URL. | ||
* @param fonts {?string[]} List of font URLs. Files should have typeface.js format. Fonts are | ||
* used in the specified order, each one is checked until necessary glyph is found. Text is not | ||
* rendered if fonts are not specified. | ||
* @param progressCbk {?Function} (phase, processedSize, totalSize) | ||
* Possible phase values: | ||
* * "font" | ||
* * "fetch" | ||
* * "parse" | ||
* * "prepare" | ||
* @param workerFactory {?Function} Factory for worker creation. The worker script should | ||
* invoke DxfViewer.SetupWorker() function. | ||
*/ | ||
async Load({url, fonts = null, progressCbk = null, workerFactory = null}) { | ||
if (url === null || url === undefined) { | ||
throw new Error("`url` parameter is not specified") | ||
} | ||
/** Load DXF into the viewer. Old content is discarded, state is reset. | ||
* @param url {string} DXF file URL. | ||
* @param fonts {?string[]} List of font URLs. Files should have typeface.js format. Fonts are | ||
* used in the specified order, each one is checked until necessary glyph is found. Text is not | ||
* rendered if fonts are not specified. | ||
* @param progressCbk {?Function} (phase, processedSize, totalSize) | ||
* Possible phase values: | ||
* * "font" | ||
* * "fetch" | ||
* * "parse" | ||
* * "prepare" | ||
* @param workerFactory {?Function} Factory for worker creation. The worker script should | ||
* invoke DxfViewer.SetupWorker() function. | ||
*/ | ||
async Load({ url, fonts = null, progressCbk = null, workerFactory = null }) { | ||
if (url === null || url === undefined) { | ||
throw new Error('`url` parameter is not specified'); | ||
} | ||
this._EnsureRenderer() | ||
this._EnsureRenderer(); | ||
this.Clear() | ||
this.Clear(); | ||
this.worker = new DxfWorker(workerFactory ? workerFactory() : null) | ||
const {scene, sceneData, dxf} = await this.worker.Load(url, fonts, this.options, progressCbk) | ||
await this.worker.Destroy() | ||
this.worker = null | ||
this.parsedDxf = dxf | ||
this.worker = new DxfWorker(workerFactory ? workerFactory() : null); | ||
const { scene, sceneData, dxf } = await this.worker.Load(url, fonts, this.options, progressCbk); | ||
await this.worker.Destroy(); | ||
this.worker = null; | ||
this.parsedDxf = dxf; | ||
this.origin = scene.origin | ||
this.bounds = scene.bounds | ||
this.hasMissingChars = scene.hasMissingChars | ||
this.origin = scene.origin; | ||
this.bounds = scene.bounds; | ||
this.hasMissingChars = scene.hasMissingChars; | ||
this.sceneData = sceneData | ||
this.sceneData = sceneData; | ||
for (const layer of scene.layers) { | ||
this.layers.set(layer.name, new Layer(layer.name, layer.displayName, layer.color)) | ||
} | ||
for (const layer of scene.layers) { | ||
this.layers.set(layer.name, new Layer(layer.name, layer.displayName, layer.color)); | ||
} | ||
/* Load all blocks on the first pass. */ | ||
for (const batch of scene.batches) { | ||
if (batch.key.blockName !== null && | ||
batch.key.geometryType !== BatchingKey.GeometryType.BLOCK_INSTANCE && | ||
batch.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE) { | ||
/* Load all blocks on the first pass. */ | ||
for (const batch of scene.batches) { | ||
if ( | ||
batch.key.blockName !== null && | ||
batch.key.geometryType !== BatchingKey.GeometryType.BLOCK_INSTANCE && | ||
batch.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE | ||
) { | ||
let block = this.blocks.get(batch.key.blockName); | ||
let block = this.blocks.get(batch.key.blockName) | ||
if (!block) { | ||
block = new Block() | ||
this.blocks.set(batch.key.blockName, block) | ||
} | ||
if (!block) { | ||
block = new Block(); | ||
this.blocks.set(batch.key.blockName, block); | ||
} | ||
const positions = this.GetBatchPositions(batch.key.blockName, scene) | ||
const positions = this.GetBatchPositions(batch.key.blockName, scene); | ||
block.PushBatch(new Batch(this, scene, { | ||
...batch, | ||
positions, | ||
}, batch.key.blockName)) | ||
} | ||
} | ||
block.PushBatch( | ||
new Batch( | ||
this, | ||
scene, | ||
{ | ||
...batch, | ||
positions, | ||
}, | ||
batch.key.blockName, | ||
), | ||
); | ||
} | ||
} | ||
console.log(`DXF scene: | ||
console.log(`DXF scene: | ||
${scene.batches.length} batches, | ||
@@ -234,609 +241,622 @@ ${this.layers.size} layers, | ||
indices ${scene.indices.byteLength} B | ||
transforms ${scene.transforms.byteLength} B`) | ||
transforms ${scene.transforms.byteLength} B`); | ||
/* Instantiate all entities. */ | ||
for (const batch of scene.batches) { | ||
this._LoadBatch(scene, batch) | ||
} | ||
/* Instantiate all entities. */ | ||
for (const batch of scene.batches) { | ||
this._LoadBatch(scene, batch); | ||
} | ||
const bypass = true | ||
const bypass = true; | ||
// console.log('sceneData.insertBlocks', ) | ||
// console.log('sceneData.insertBlocks', ) | ||
//XXX line type | ||
//XXX line type | ||
for (const [key, { batches }] of sceneData.insertBlocks.entries()) { | ||
const handledObjects = []; | ||
for (const [key, { batches }] of sceneData.insertBlocks.entries()) { | ||
const handledObjects = [] | ||
for (const batch of batches) { | ||
if (batch.chunks) { | ||
for (const chunk of batch.chunks) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: chunk.indices, | ||
vertices: chunk.vertices, | ||
}); | ||
for (const batch of batches) { | ||
if (batch.chunks) { | ||
for (const chunk of batch.chunks) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: chunk.indices, | ||
vertices: chunk.vertices, | ||
}) | ||
handledObjects.push(object); | ||
} | ||
} else { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: null, | ||
vertices: batch.vertices, | ||
}); | ||
handledObjects.push(object) | ||
} | ||
} else { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: null, | ||
vertices: batch.vertices, | ||
}) | ||
handledObjects.push(object) | ||
} | ||
} | ||
this.objects.set(key, handledObjects) | ||
handledObjects.push(object); | ||
} | ||
for (const [key, { batches }] of this.blocks.entries()) { | ||
if (this.objects.has(key)) { | ||
continue | ||
} | ||
} | ||
const block = sceneData.blocks.get(key); | ||
this.objects.set(key, handledObjects); | ||
} | ||
const handledObjects = [] | ||
for (const [key, { batches }] of this.blocks.entries()) { | ||
if (this.objects.has(key)) { | ||
continue; | ||
} | ||
for (const batch of batches) { | ||
if (batch.chunks) { | ||
for (const chunk of batch.chunks) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: chunk.indices, | ||
vertices: chunk.vertices, | ||
}) | ||
const block = sceneData.blocks ? sceneData.blocks.get(key) : undefined; | ||
const handledObjects = []; | ||
object.userData.block = block; | ||
for (const batch of batches) { | ||
if (batch.chunks) { | ||
for (const chunk of batch.chunks) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: chunk.indices, | ||
vertices: chunk.vertices, | ||
}); | ||
handledObjects.push(object) | ||
} | ||
} else { | ||
if (batch.positions.size === 0) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: null, | ||
vertices: batch.vertices, | ||
}) | ||
object.userData.block = block; | ||
object.userData.block = block; | ||
handledObjects.push(object); | ||
} | ||
} else { | ||
if (batch.positions.size === 0) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: null, | ||
vertices: batch.vertices, | ||
}); | ||
handledObjects.push(object) | ||
object.userData.block = block; | ||
continue; | ||
} | ||
handledObjects.push(object); | ||
const [key, { block: positionBlock }] = Array.from(batch.positions)[0]; | ||
const blockBatch = positionBlock.batches[0]; | ||
continue; | ||
} | ||
if (blockBatch.chunks) { | ||
for (const chunk of blockBatch.chunks) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: chunk.indices, | ||
vertices: chunk.vertices, | ||
}) | ||
const [_key, { block: positionBlock }] = Array.from(batch.positions)[0]; | ||
const blockBatch = positionBlock.batches[0]; | ||
object.userData.block = block; | ||
if (blockBatch.chunks) { | ||
for (const chunk of blockBatch.chunks) { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: chunk.indices, | ||
vertices: chunk.vertices, | ||
}); | ||
handledObjects.push(object) | ||
} | ||
} else { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: null, | ||
vertices: blockBatch.vertices, | ||
}) | ||
object.userData.block = block; | ||
object.userData.block = block; | ||
handledObjects.push(object) | ||
} | ||
} | ||
handledObjects.push(object); | ||
} | ||
} else { | ||
const object = this.CreateViewerObject({ | ||
batch, | ||
indices: null, | ||
vertices: blockBatch.vertices, | ||
}); | ||
this.objects.set(key, handledObjects) | ||
object.userData.block = block; | ||
handledObjects.push(object); | ||
} | ||
} | ||
} | ||
this._Emit("loaded") | ||
this.objects.set(key, handledObjects); | ||
} | ||
if (scene.bounds) { | ||
this.FitView(scene.bounds.minX - scene.origin.x, scene.bounds.maxX - scene.origin.x, | ||
scene.bounds.minY - scene.origin.y, scene.bounds.maxY - scene.origin.y) | ||
} else { | ||
this._Message("Empty document", MessageLevel.WARN) | ||
} | ||
this._Emit('loaded'); | ||
if (this.hasMissingChars) { | ||
this._Message("Some characters cannot be properly displayed due to missing fonts", | ||
MessageLevel.WARN) | ||
} | ||
if (scene.bounds) { | ||
this.FitView( | ||
scene.bounds.minX - scene.origin.x, | ||
scene.bounds.maxX - scene.origin.x, | ||
scene.bounds.minY - scene.origin.y, | ||
scene.bounds.maxY - scene.origin.y, | ||
); | ||
} else { | ||
this._Message('Empty document', MessageLevel.WARN); | ||
} | ||
this._CreateControls() | ||
this.Render() | ||
if (this.hasMissingChars) { | ||
this._Message( | ||
'Some characters cannot be properly displayed due to missing fonts', | ||
MessageLevel.WARN, | ||
); | ||
} | ||
CreateViewerObject({ | ||
batch, | ||
indices, | ||
vertices, | ||
}) { | ||
const color = batch.key.color | ||
const materialFactory = | ||
batch.key.geometryType === BatchingKey.GeometryType.POINTS || | ||
batch.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE ? | ||
this._GetSimplePointMaterial : this._GetSimpleColorMaterial | ||
this._CreateControls(); | ||
this.Render(); | ||
} | ||
const material = materialFactory.call(this, this._TransformColor(color), InstanceType.NONE) | ||
CreateViewerObject({ batch, indices, vertices }) { | ||
const color = batch.key.color; | ||
const materialFactory = | ||
batch.key.geometryType === BatchingKey.GeometryType.POINTS || | ||
batch.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE | ||
? this._GetSimplePointMaterial | ||
: this._GetSimpleColorMaterial; | ||
let objConstructor | ||
switch (batch.key.geometryType) { | ||
case BatchingKey.GeometryType.POINTS: | ||
/* This method also called for creating dots for shaped point instances. */ | ||
case BatchingKey.GeometryType.POINT_INSTANCE: | ||
objConstructor = three.Points | ||
break | ||
case BatchingKey.GeometryType.LINES: | ||
case BatchingKey.GeometryType.INDEXED_LINES: | ||
objConstructor = three.LineSegments | ||
break | ||
case BatchingKey.GeometryType.TRIANGLES: | ||
case BatchingKey.GeometryType.INDEXED_TRIANGLES: | ||
objConstructor = three.Mesh | ||
break | ||
default: | ||
throw new Error("Unexpected geometry type:" + batch.key.geometryType) | ||
} | ||
const material = materialFactory.call(this, this._TransformColor(color), InstanceType.NONE); | ||
const geometry = new three.BufferGeometry() | ||
const positionAttribute = new three.Float32BufferAttribute(vertices.buffer, 2); | ||
let objConstructor; | ||
switch (batch.key.geometryType) { | ||
case BatchingKey.GeometryType.POINTS: | ||
/* This method also called for creating dots for shaped point instances. */ | ||
case BatchingKey.GeometryType.POINT_INSTANCE: | ||
objConstructor = three.Points; | ||
break; | ||
case BatchingKey.GeometryType.LINES: | ||
case BatchingKey.GeometryType.INDEXED_LINES: | ||
objConstructor = three.LineSegments; | ||
break; | ||
case BatchingKey.GeometryType.TRIANGLES: | ||
case BatchingKey.GeometryType.INDEXED_TRIANGLES: | ||
objConstructor = three.Mesh; | ||
break; | ||
default: | ||
throw new Error('Unexpected geometry type:' + batch.key.geometryType); | ||
} | ||
geometry.setAttribute('position', positionAttribute); | ||
const geometry = new three.BufferGeometry(); | ||
const positionAttribute = new three.Float32BufferAttribute(vertices.buffer, 2); | ||
if (indices) { | ||
const handledIndices = new three.Uint16BufferAttribute(indices.buffer, 1) | ||
geometry.setIndex(handledIndices) | ||
} | ||
geometry.setAttribute('position', positionAttribute); | ||
return new objConstructor(geometry, material); | ||
if (indices) { | ||
const handledIndices = new three.Uint16BufferAttribute(indices.buffer, 1); | ||
geometry.setIndex(handledIndices); | ||
} | ||
Render() { | ||
this._EnsureRenderer() | ||
this.renderer.render(this.scene, this.camera) | ||
} | ||
return new objConstructor(geometry, material); | ||
} | ||
/** @return {Iterable<{name:String, color:number}>} List of layer names. */ | ||
GetLayers() { | ||
const result = [] | ||
for (const lyr of this.layers.values()) { | ||
result.push({ | ||
name: lyr.name, | ||
displayName: lyr.displayName, | ||
color: this._TransformColor(lyr.color) | ||
}) | ||
} | ||
return result | ||
} | ||
Render() { | ||
this._EnsureRenderer(); | ||
this.renderer.render(this.scene, this.camera); | ||
} | ||
ShowLayer(name, show) { | ||
this._EnsureRenderer() | ||
const layer = this.layers.get(name) | ||
if (!layer) { | ||
return | ||
} | ||
for (const obj of layer.objects) { | ||
obj.visible = show | ||
} | ||
this.Render() | ||
/** @return {Iterable<{name:String, color:number}>} List of layer names. */ | ||
GetLayers() { | ||
const result = []; | ||
for (const lyr of this.layers.values()) { | ||
result.push({ | ||
name: lyr.name, | ||
displayName: lyr.displayName, | ||
color: this._TransformColor(lyr.color), | ||
}); | ||
} | ||
return result; | ||
} | ||
/** Reset the viewer state. */ | ||
Clear() { | ||
this._EnsureRenderer() | ||
if (this.worker) { | ||
this.worker.Destroy(true) | ||
this.worker = null | ||
} | ||
if (this.controls) { | ||
this.controls.dispose() | ||
this.controls = null | ||
} | ||
this.scene.clear() | ||
for (const layer of this.layers.values()) { | ||
layer.Dispose() | ||
} | ||
this.layers.clear() | ||
this.blocks.clear() | ||
this.materials.each(e => e.material.dispose()) | ||
this.materials.clear() | ||
this.SetView({x: 0, y: 0}, 2) | ||
this._Emit("cleared") | ||
this.Render() | ||
ShowLayer(name, show) { | ||
this._EnsureRenderer(); | ||
const layer = this.layers.get(name); | ||
if (!layer) { | ||
return; | ||
} | ||
/** Free all resources. The viewer object should not be used after this method was called. */ | ||
Destroy() { | ||
if (!this.HasRenderer()) { | ||
return | ||
} | ||
if (this.resizeObserver) { | ||
this.resizeObserver.disconnect() | ||
} | ||
this.Clear() | ||
this._Emit("destroyed") | ||
for (const m of this.simplePointMaterial) { | ||
m.dispose() | ||
} | ||
for (const m of this.simpleColorMaterial) { | ||
m.dispose() | ||
} | ||
this.simplePointMaterial = null | ||
this.simpleColorMaterial = null | ||
this.renderer.dispose() | ||
this.renderer = null | ||
for (const obj of layer.objects) { | ||
obj.visible = show; | ||
} | ||
this.Render(); | ||
} | ||
SetView(center, width) { | ||
const aspect = this.canvasWidth / this.canvasHeight | ||
const height = width / aspect | ||
const cam = this.camera | ||
cam.left = -width / 2 | ||
cam.right = width / 2 | ||
cam.top = height / 2 | ||
cam.bottom = -height / 2 | ||
cam.zoom = 1 | ||
cam.position.set(center.x, center.y, 1) | ||
cam.rotation.set(0, 0, 0) | ||
cam.updateMatrix() | ||
cam.updateProjectionMatrix() | ||
this._Emit("viewChanged") | ||
/** Reset the viewer state. */ | ||
Clear() { | ||
this._EnsureRenderer(); | ||
if (this.worker) { | ||
this.worker.Destroy(true); | ||
this.worker = null; | ||
} | ||
/** Set view to fit the specified bounds. */ | ||
FitView(minX, maxX, minY, maxY, padding = 0.1) { | ||
const aspect = this.canvasWidth / this.canvasHeight | ||
let width = maxX - minX | ||
const height = maxY - minY | ||
const center = {x: minX + width / 2, y: minY + height / 2} | ||
if (height * aspect > width) { | ||
width = height * aspect | ||
} | ||
if (width <= Number.MIN_VALUE * 2) { | ||
width = 1 | ||
} | ||
this.SetView(center, width * (1 + padding)) | ||
if (this.controls) { | ||
this.controls.dispose(); | ||
this.controls = null; | ||
} | ||
/** @return {Scene} three.js scene for the viewer. Can be used to add custom entities on the | ||
* scene. Remember to apply scene origin available via GetOrigin() method. | ||
*/ | ||
GetScene() { | ||
return this.scene | ||
this.scene.clear(); | ||
for (const layer of this.layers.values()) { | ||
layer.Dispose(); | ||
} | ||
this.layers.clear(); | ||
this.blocks.clear(); | ||
this.materials.each((e) => e.material.dispose()); | ||
this.materials.clear(); | ||
this.SetView({ x: 0, y: 0 }, 2); | ||
this._Emit('cleared'); | ||
this.Render(); | ||
} | ||
/** @return {OrthographicCamera} three.js camera for the viewer. */ | ||
GetCamera() { | ||
return this.camera | ||
/** Free all resources. The viewer object should not be used after this method was called. */ | ||
Destroy() { | ||
if (!this.HasRenderer()) { | ||
return; | ||
} | ||
/** @return {Vector2} Scene origin in global drawing coordinates. */ | ||
GetOrigin() { | ||
return this.origin | ||
if (this.resizeObserver) { | ||
this.resizeObserver.disconnect(); | ||
} | ||
/** | ||
* @return {?{maxX: number, maxY: number, minX: number, minY: number}} Scene bounds in model | ||
* space coordinates. Null if empty scene. | ||
*/ | ||
GetBounds() { | ||
return this.bounds | ||
this.Clear(); | ||
this._Emit('destroyed'); | ||
for (const m of this.simplePointMaterial) { | ||
m.dispose(); | ||
} | ||
GetTexts() { | ||
return this.sceneData.texts; | ||
for (const m of this.simpleColorMaterial) { | ||
m.dispose(); | ||
} | ||
this.simplePointMaterial = null; | ||
this.simpleColorMaterial = null; | ||
this.renderer.dispose(); | ||
this.renderer = null; | ||
} | ||
/** Subscribe to the specified event. The following events are defined: | ||
* * "loaded" - new scene loaded. | ||
* * "cleared" - current scene cleared. | ||
* * "destroyed" - viewer instance destroyed. | ||
* * "resized" - viewport size changed. Details: {width, height} | ||
* * "pointerdown" - Details: {domEvent, position:{x,y}}, position is in scene coordinates. | ||
* * "pointerup" | ||
* * "viewChanged" | ||
* * "message" - Some message from the viewer. {message: string, level: string}. | ||
* | ||
* @param eventName {string} | ||
* @param eventHandler {function} Accepts event object. | ||
*/ | ||
Subscribe(eventName, eventHandler) { | ||
this._EnsureRenderer() | ||
this.canvas.addEventListener(EVENT_NAME_PREFIX + eventName, eventHandler) | ||
} | ||
SetView(center, width) { | ||
const aspect = this.canvasWidth / this.canvasHeight; | ||
const height = width / aspect; | ||
const cam = this.camera; | ||
cam.left = -width / 2; | ||
cam.right = width / 2; | ||
cam.top = height / 2; | ||
cam.bottom = -height / 2; | ||
cam.zoom = 1; | ||
cam.position.set(center.x, center.y, 1); | ||
cam.rotation.set(0, 0, 0); | ||
cam.updateMatrix(); | ||
cam.updateProjectionMatrix(); | ||
this._Emit('viewChanged'); | ||
} | ||
/** Unsubscribe from previously subscribed event. The arguments should match previous | ||
* Subscribe() call. | ||
* | ||
* @param eventName {string} | ||
* @param eventHandler {function} | ||
*/ | ||
Unsubscribe(eventName, eventHandler) { | ||
this._EnsureRenderer() | ||
this.canvas.removeEventListener(EVENT_NAME_PREFIX + eventName, eventHandler) | ||
/** Set view to fit the specified bounds. */ | ||
FitView(minX, maxX, minY, maxY, padding = 0.1) { | ||
const aspect = this.canvasWidth / this.canvasHeight; | ||
let width = maxX - minX; | ||
const height = maxY - minY; | ||
const center = { x: minX + width / 2, y: minY + height / 2 }; | ||
if (height * aspect > width) { | ||
width = height * aspect; | ||
} | ||
if (width <= Number.MIN_VALUE * 2) { | ||
width = 1; | ||
} | ||
this.SetView(center, width * (1 + padding)); | ||
} | ||
// ///////////////////////////////////////////////////////////////////////////////////////////// | ||
/** @return {Scene} three.js scene for the viewer. Can be used to add custom entities on the | ||
* scene. Remember to apply scene origin available via GetOrigin() method. | ||
*/ | ||
GetScene() { | ||
return this.scene; | ||
} | ||
_EnsureRenderer() { | ||
if (!this.HasRenderer()) { | ||
throw new Error("WebGL renderer not available. " + | ||
"Probable WebGL context loss, try refreshing the page.") | ||
} | ||
} | ||
/** @return {OrthographicCamera} three.js camera for the viewer. */ | ||
GetCamera() { | ||
return this.camera; | ||
} | ||
_CreateControls() { | ||
const controls = this.controls = new OrbitControls(this.camera, this.canvas) | ||
controls.enableRotate = false | ||
controls.mouseButtons = { | ||
LEFT: three.MOUSE.PAN, | ||
MIDDLE: three.MOUSE.DOLLY | ||
} | ||
controls.touches = { | ||
ONE: three.TOUCH.PAN, | ||
TWO: three.TOUCH.DOLLY_PAN | ||
} | ||
controls.zoomSpeed = 3 | ||
controls.mouseZoomSpeedFactor = 0.05 | ||
controls.target = new three.Vector3(this.camera.position.x, this.camera.position.y, 0) | ||
controls.addEventListener("change", () => { | ||
this._Emit("viewChanged") | ||
this.Render() | ||
}) | ||
controls.update() | ||
} | ||
/** @return {Vector2} Scene origin in global drawing coordinates. */ | ||
GetOrigin() { | ||
return this.origin; | ||
} | ||
_Emit(eventName, data = null) { | ||
this.canvas.dispatchEvent(new CustomEvent(EVENT_NAME_PREFIX + eventName, { detail: data })) | ||
} | ||
/** | ||
* @return {?{maxX: number, maxY: number, minX: number, minY: number}} Scene bounds in model | ||
* space coordinates. Null if empty scene. | ||
*/ | ||
GetBounds() { | ||
return this.bounds; | ||
} | ||
_Message(message, level = MessageLevel.INFO) { | ||
this._Emit("message", {message, level}) | ||
GetTexts() { | ||
return this.sceneData.texts; | ||
} | ||
/** Subscribe to the specified event. The following events are defined: | ||
* * "loaded" - new scene loaded. | ||
* * "cleared" - current scene cleared. | ||
* * "destroyed" - viewer instance destroyed. | ||
* * "resized" - viewport size changed. Details: {width, height} | ||
* * "pointerdown" - Details: {domEvent, position:{x,y}}, position is in scene coordinates. | ||
* * "pointerup" | ||
* * "viewChanged" | ||
* * "message" - Some message from the viewer. {message: string, level: string}. | ||
* | ||
* @param eventName {string} | ||
* @param eventHandler {function} Accepts event object. | ||
*/ | ||
Subscribe(eventName, eventHandler) { | ||
this._EnsureRenderer(); | ||
this.canvas.addEventListener(EVENT_NAME_PREFIX + eventName, eventHandler); | ||
} | ||
/** Unsubscribe from previously subscribed event. The arguments should match previous | ||
* Subscribe() call. | ||
* | ||
* @param eventName {string} | ||
* @param eventHandler {function} | ||
*/ | ||
Unsubscribe(eventName, eventHandler) { | ||
this._EnsureRenderer(); | ||
this.canvas.removeEventListener(EVENT_NAME_PREFIX + eventName, eventHandler); | ||
} | ||
// ///////////////////////////////////////////////////////////////////////////////////////////// | ||
_EnsureRenderer() { | ||
if (!this.HasRenderer()) { | ||
throw new Error( | ||
'WebGL renderer not available. ' + 'Probable WebGL context loss, try refreshing the page.', | ||
); | ||
} | ||
} | ||
_OnPointerEvent(e) { | ||
const canvasRect = e.target.getBoundingClientRect() | ||
const canvasCoord = {x: e.clientX - canvasRect.left, y: e.clientY - canvasRect.top} | ||
this._Emit(e.type, { | ||
domEvent: e, | ||
canvasCoord, | ||
position: this.CanvasToSceneCoord(canvasCoord.x, canvasCoord.y) | ||
}) | ||
const position = this.CanvasToSceneCoord(canvasCoord.x, canvasCoord.y) | ||
_CreateControls() { | ||
const controls = (this.controls = new OrbitControls(this.camera, this.canvas)); | ||
controls.enableRotate = false; | ||
controls.mouseButtons = { | ||
LEFT: three.MOUSE.PAN, | ||
MIDDLE: three.MOUSE.DOLLY, | ||
}; | ||
controls.touches = { | ||
ONE: three.TOUCH.PAN, | ||
TWO: three.TOUCH.DOLLY_PAN, | ||
}; | ||
controls.zoomSpeed = 3; | ||
controls.mouseZoomSpeedFactor = 0.05; | ||
controls.target = new three.Vector3(this.camera.position.x, this.camera.position.y, 0); | ||
controls.addEventListener('change', () => { | ||
this._Emit('viewChanged'); | ||
this.Render(); | ||
}); | ||
controls.update(); | ||
} | ||
// function convertVerticesToArray(vertices) { | ||
// const coordinates = []; | ||
// for (let i = 0; i < vertices.length; i += 3) { | ||
// coordinates.push({ | ||
// x: vertices[i], | ||
// y: vertices[i + 1], | ||
// z: vertices[i + 2] | ||
// }); | ||
// } | ||
// return coordinates; | ||
// } | ||
_Emit(eventName, data = null) { | ||
this.canvas.dispatchEvent(new CustomEvent(EVENT_NAME_PREFIX + eventName, { detail: data })); | ||
} | ||
// const senOusby = this.blocks.get('1 FURN L1$0$SEN OUSBY CH 1') | ||
_Message(message, level = MessageLevel.INFO) { | ||
this._Emit('message', { message, level }); | ||
} | ||
// console.log('blocks', senOusby) | ||
_OnPointerEvent(e) { | ||
const canvasRect = e.target.getBoundingClientRect(); | ||
const canvasCoord = { x: e.clientX - canvasRect.left, y: e.clientY - canvasRect.top }; | ||
this._Emit(e.type, { | ||
domEvent: e, | ||
canvasCoord, | ||
position: this.CanvasToSceneCoord(canvasCoord.x, canvasCoord.y), | ||
}); | ||
const position = this.CanvasToSceneCoord(canvasCoord.x, canvasCoord.y); | ||
// console.log('position', this._CanvasToSceneCoord(canvasCoord.x, canvasCoord.y)) | ||
} | ||
// function convertVerticesToArray(vertices) { | ||
// const coordinates = []; | ||
// for (let i = 0; i < vertices.length; i += 3) { | ||
// coordinates.push({ | ||
// x: vertices[i], | ||
// y: vertices[i + 1], | ||
// z: vertices[i + 2] | ||
// }); | ||
// } | ||
// return coordinates; | ||
// } | ||
/** @return {{x,y}} Scene coordinate corresponding to the specified canvas pixel coordinates. */ | ||
CanvasToSceneCoord(x, y) { | ||
const v = new three.Vector3(x * 2 / this.canvasWidth - 1, | ||
-y * 2 / this.canvasHeight + 1, | ||
1).unproject(this.camera) | ||
return {x: v.x, y: v.y} | ||
} | ||
// const senOusby = this.blocks.get('1 FURN L1$0$SEN OUSBY CH 1') | ||
_OnResize(entry) { | ||
this.SetSize(Math.floor(entry.contentRect.width), Math.floor(entry.contentRect.height)) | ||
} | ||
// console.log('blocks', senOusby) | ||
GetBatchPositions(blockName, scene) { | ||
const positions = new Map() | ||
// console.log('position', this._CanvasToSceneCoord(canvasCoord.x, canvasCoord.y)) | ||
} | ||
const block = this.sceneData?.blocks?.get?.(blockName) | ||
const factor = this.sceneData?.vars?.get?.('DIMALTF') | ||
/** @return {{x,y}} Scene coordinate corresponding to the specified canvas pixel coordinates. */ | ||
CanvasToSceneCoord(x, y) { | ||
const v = new three.Vector3( | ||
(x * 2) / this.canvasWidth - 1, | ||
(-y * 2) / this.canvasHeight + 1, | ||
1, | ||
).unproject(this.camera); | ||
// console.log('factor', factor) | ||
return { x: v.x, y: v.y }; | ||
} | ||
if (block) { | ||
const entities = block?.data?.entities ?? [] | ||
for (let i = 0; i < entities.length; i++) { | ||
const entity = entities[i]; | ||
_OnResize(entry) { | ||
this.SetSize(Math.floor(entry.contentRect.width), Math.floor(entry.contentRect.height)); | ||
} | ||
// console.log('!!! insert', this.sceneData.unmappedInserts.get(`${blockName}${i + 1}`)) | ||
// console.log('*** ousby', entities[0].vertices) | ||
// if (entity.position) { | ||
// console.log('there is a position!') | ||
// // console.log('*** position', entity.position) | ||
// } | ||
GetBatchPositions(blockName, scene) { | ||
const positions = new Map(); | ||
const name = `${blockName}__${i + 1}`; | ||
const block = this.sceneData?.blocks?.get?.(blockName); | ||
const factor = this.sceneData?.vars?.get?.('DIMALTF'); | ||
if (entity.vertices) { | ||
positions.set(name, { | ||
block, | ||
entity: this.sceneData.unmappedInserts.get(name), | ||
factor, | ||
}) | ||
} | ||
} | ||
} | ||
// const entities = block?.data?.entities | ||
// console.log('factor', factor) | ||
// if (!entities) { | ||
// return null | ||
// } | ||
if (block) { | ||
const entities = block?.data?.entities ?? []; | ||
// for (let i = 0; i < entities.length; i++) { | ||
// const entity = entities[i] | ||
for (let i = 0; i < entities.length; i++) { | ||
const entity = entities[i]; | ||
// positions.push(entity) | ||
// console.log('!!! insert', this.sceneData.unmappedInserts.get(`${blockName}${i + 1}`)) | ||
// console.log('*** ousby', entities[0].vertices) | ||
// if (entity.position) { | ||
// console.log('there is a position!') | ||
// // console.log('*** position', entity.position) | ||
// } | ||
return positions | ||
const name = `${blockName}__${i + 1}`; | ||
if (entity.vertices) { | ||
positions.set(name, { | ||
block, | ||
entity: this.sceneData.unmappedInserts.get(name), | ||
factor, | ||
}); | ||
} | ||
} | ||
} | ||
// const entities = block?.data?.entities | ||
_LoadBatch(scene, batch) { | ||
// console.log('keys', batch.key.blockName) | ||
const positions = this.GetBatchPositions(batch.key.blockName, scene) | ||
// if (batch.key.insertName) { | ||
// const objects = new Batch(this, scene, { | ||
// ...batch, | ||
// positions, | ||
// }, batch.key.insertName).CreateObjects() | ||
// if (!entities) { | ||
// return null | ||
// } | ||
// for (const obj of objects) { | ||
// obj.insertName = batch.key.insertName | ||
// for (let i = 0; i < entities.length; i++) { | ||
// const entity = entities[i] | ||
// for (const [key] of this.sceneData.unmappedInserts.entries()) { | ||
// if (key.startsWith(`${batch.key.insertName}__`) || key === batch.key.insertName) { | ||
// // console.log('key', key) | ||
// this.objects.set(key, { | ||
// obj, | ||
// }) | ||
// } | ||
// } | ||
// } | ||
// } | ||
// positions.push(entity) | ||
// } | ||
if (batch.key.blockName !== null && | ||
batch.key.geometryType !== BatchingKey.GeometryType.BLOCK_INSTANCE && | ||
batch.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE) { | ||
/* Block definition. */ | ||
return | ||
} | ||
const objects = new Batch(this, scene, { | ||
...batch, | ||
positions, | ||
}, batch.key.blockName).CreateObjects() | ||
return positions; | ||
} | ||
const layer = this.layers.get(batch.key.layerName) | ||
_LoadBatch(scene, batch) { | ||
// console.log('keys', batch.key.blockName) | ||
const positions = this.GetBatchPositions(batch.key.blockName, scene); | ||
// if (batch.key.insertName) { | ||
// const objects = new Batch(this, scene, { | ||
// ...batch, | ||
// positions, | ||
// }, batch.key.insertName).CreateObjects() | ||
for (const obj of objects) { | ||
obj.insertName = batch.key.blockName | ||
// for (const obj of objects) { | ||
// obj.insertName = batch.key.insertName | ||
this.scene.add(obj) | ||
if (layer) { | ||
layer.PushObject(obj) | ||
} | ||
} | ||
} | ||
// for (const [key] of this.sceneData.unmappedInserts.entries()) { | ||
// if (key.startsWith(`${batch.key.insertName}__`) || key === batch.key.insertName) { | ||
// // console.log('key', key) | ||
// this.objects.set(key, { | ||
// obj, | ||
// }) | ||
// } | ||
// } | ||
// } | ||
// } | ||
_GetSimpleColorMaterial(color, instanceType = InstanceType.NONE) { | ||
const key = new MaterialKey(instanceType, null, color, 0) | ||
let entry = this.materials.find({key}) | ||
if (entry !== null) { | ||
return entry.material | ||
} | ||
entry = { | ||
key, | ||
material: this._CreateSimpleColorMaterialInstance(color, instanceType) | ||
} | ||
this.materials.insert(entry) | ||
return entry.material | ||
if ( | ||
batch.key.blockName !== null && | ||
batch.key.geometryType !== BatchingKey.GeometryType.BLOCK_INSTANCE && | ||
batch.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE | ||
) { | ||
/* Block definition. */ | ||
return; | ||
} | ||
_CreateSimpleColorMaterial(instanceType = InstanceType.NONE) { | ||
const shaders = this._GenerateShaders(instanceType, false) | ||
return new three.RawShaderMaterial({ | ||
uniforms: { | ||
color: { | ||
value: new three.Color(0xff00ff) | ||
} | ||
}, | ||
vertexShader: shaders.vertex, | ||
fragmentShader: shaders.fragment, | ||
depthTest: false, | ||
depthWrite: false, | ||
glslVersion: three.GLSL3, | ||
side: three.DoubleSide | ||
}) | ||
} | ||
const objects = new Batch( | ||
this, | ||
scene, | ||
{ | ||
...batch, | ||
positions, | ||
}, | ||
batch.key.blockName, | ||
).CreateObjects(); | ||
/** @param color {number} Color RGB numeric value. | ||
* @param instanceType {number} | ||
*/ | ||
_CreateSimpleColorMaterialInstance(color, instanceType = InstanceType.NONE) { | ||
const src = this.simpleColorMaterial[instanceType] | ||
/* Should reuse compiled shaders. */ | ||
const m = src.clone() | ||
m.uniforms.color = { value: new three.Color(color) } | ||
return m | ||
} | ||
const layer = this.layers.get(batch.key.layerName); | ||
_GetSimplePointMaterial(color, instanceType = InstanceType.NONE) { | ||
const key = new MaterialKey(instanceType, BatchingKey.GeometryType.POINTS, color, 0) | ||
let entry = this.materials.find({key}) | ||
if (entry !== null) { | ||
return entry.material | ||
} | ||
entry = { | ||
key, | ||
material: this._CreateSimplePointMaterialInstance(color, this.options.pointSize, | ||
instanceType) | ||
} | ||
this.materials.insert(entry) | ||
return entry.material | ||
for (const obj of objects) { | ||
obj.insertName = batch.key.blockName; | ||
this.scene.add(obj); | ||
if (layer) { | ||
layer.PushObject(obj); | ||
} | ||
} | ||
} | ||
_CreateSimplePointMaterial(instanceType = InstanceType.NONE) { | ||
const shaders = this._GenerateShaders(instanceType, true) | ||
return new three.RawShaderMaterial({ | ||
uniforms: { | ||
color: { | ||
value: new three.Color(0xff00ff) | ||
}, | ||
pointSize: { | ||
value: 2 | ||
} | ||
}, | ||
vertexShader: shaders.vertex, | ||
fragmentShader: shaders.fragment, | ||
depthTest: false, | ||
depthWrite: false, | ||
glslVersion: three.GLSL3 | ||
}) | ||
_GetSimpleColorMaterial(color, instanceType = InstanceType.NONE) { | ||
const key = new MaterialKey(instanceType, null, color, 0); | ||
let entry = this.materials.find({ key }); | ||
if (entry !== null) { | ||
return entry.material; | ||
} | ||
entry = { | ||
key, | ||
material: this._CreateSimpleColorMaterialInstance(color, instanceType), | ||
}; | ||
this.materials.insert(entry); | ||
return entry.material; | ||
} | ||
/** @param color {number} Color RGB numeric value. | ||
* @param size {number} Rasterized point size in pixels. | ||
* @param instanceType {number} | ||
*/ | ||
_CreateSimplePointMaterialInstance(color, size = 2, instanceType = InstanceType.NONE) { | ||
const src = this.simplePointMaterial[instanceType] | ||
/* Should reuse compiled shaders. */ | ||
const m = src.clone() | ||
m.uniforms.color = { value: new three.Color(color) } | ||
m.uniforms.size = { value: size } | ||
return m | ||
_CreateSimpleColorMaterial(instanceType = InstanceType.NONE) { | ||
const shaders = this._GenerateShaders(instanceType, false); | ||
return new three.RawShaderMaterial({ | ||
uniforms: { | ||
color: { | ||
value: new three.Color(0xff00ff), | ||
}, | ||
}, | ||
vertexShader: shaders.vertex, | ||
fragmentShader: shaders.fragment, | ||
depthTest: false, | ||
depthWrite: false, | ||
glslVersion: three.GLSL3, | ||
side: three.DoubleSide, | ||
}); | ||
} | ||
/** @param color {number} Color RGB numeric value. | ||
* @param instanceType {number} | ||
*/ | ||
_CreateSimpleColorMaterialInstance(color, instanceType = InstanceType.NONE) { | ||
const src = this.simpleColorMaterial[instanceType]; | ||
/* Should reuse compiled shaders. */ | ||
const m = src.clone(); | ||
m.uniforms.color = { value: new three.Color(color) }; | ||
return m; | ||
} | ||
_GetSimplePointMaterial(color, instanceType = InstanceType.NONE) { | ||
const key = new MaterialKey(instanceType, BatchingKey.GeometryType.POINTS, color, 0); | ||
let entry = this.materials.find({ key }); | ||
if (entry !== null) { | ||
return entry.material; | ||
} | ||
entry = { | ||
key, | ||
material: this._CreateSimplePointMaterialInstance( | ||
color, | ||
this.options.pointSize, | ||
instanceType, | ||
), | ||
}; | ||
this.materials.insert(entry); | ||
return entry.material; | ||
} | ||
_GenerateShaders(instanceType, pointSize) { | ||
const fullInstanceAttr = instanceType === InstanceType.FULL ? | ||
` | ||
_CreateSimplePointMaterial(instanceType = InstanceType.NONE) { | ||
const shaders = this._GenerateShaders(instanceType, true); | ||
return new three.RawShaderMaterial({ | ||
uniforms: { | ||
color: { | ||
value: new three.Color(0xff00ff), | ||
}, | ||
pointSize: { | ||
value: 2, | ||
}, | ||
}, | ||
vertexShader: shaders.vertex, | ||
fragmentShader: shaders.fragment, | ||
depthTest: false, | ||
depthWrite: false, | ||
glslVersion: three.GLSL3, | ||
}); | ||
} | ||
/** @param color {number} Color RGB numeric value. | ||
* @param size {number} Rasterized point size in pixels. | ||
* @param instanceType {number} | ||
*/ | ||
_CreateSimplePointMaterialInstance(color, size = 2, instanceType = InstanceType.NONE) { | ||
const src = this.simplePointMaterial[instanceType]; | ||
/* Should reuse compiled shaders. */ | ||
const m = src.clone(); | ||
m.uniforms.color = { value: new three.Color(color) }; | ||
m.uniforms.size = { value: size }; | ||
return m; | ||
} | ||
_GenerateShaders(instanceType, pointSize) { | ||
const fullInstanceAttr = | ||
instanceType === InstanceType.FULL | ||
? ` | ||
/* First row. */ | ||
@@ -846,24 +866,31 @@ in vec3 instanceTransform0; | ||
in vec3 instanceTransform1; | ||
` : "" | ||
const fullInstanceTransform = instanceType === InstanceType.FULL ? | ||
` | ||
: ''; | ||
const fullInstanceTransform = | ||
instanceType === InstanceType.FULL | ||
? ` | ||
pos.xy = mat2(instanceTransform0[0], instanceTransform1[0], | ||
instanceTransform0[1], instanceTransform1[1]) * pos.xy + | ||
vec2(instanceTransform0[2], instanceTransform1[2]); | ||
` : "" | ||
` | ||
: ''; | ||
const pointInstanceAttr = instanceType === InstanceType.POINT ? | ||
` | ||
const pointInstanceAttr = | ||
instanceType === InstanceType.POINT | ||
? ` | ||
in vec2 instanceTransform; | ||
` : "" | ||
const pointInstanceTransform = instanceType === InstanceType.POINT ? | ||
` | ||
: ''; | ||
const pointInstanceTransform = | ||
instanceType === InstanceType.POINT | ||
? ` | ||
pos.xy += instanceTransform; | ||
` : "" | ||
` | ||
: ''; | ||
const pointSizeUniform = pointSize ? "uniform float pointSize;" : "" | ||
const pointSizeAssigment = pointSize ? "gl_PointSize = pointSize;" : "" | ||
const pointSizeUniform = pointSize ? 'uniform float pointSize;' : ''; | ||
const pointSizeAssigment = pointSize ? 'gl_PointSize = pointSize;' : ''; | ||
return { | ||
vertex: ` | ||
return { | ||
vertex: ` | ||
@@ -887,3 +914,3 @@ precision highp float; | ||
`, | ||
fragment: ` | ||
fragment: ` | ||
@@ -898,354 +925,364 @@ precision highp float; | ||
} | ||
` | ||
} | ||
} | ||
`, | ||
}; | ||
} | ||
/** Ensure the color is contrast enough with current background color. | ||
* @param color {number} RGB value. | ||
* @return {number} RGB value to use for rendering. | ||
*/ | ||
_TransformColor(color) { | ||
if (!this.options.colorCorrection && !this.options.blackWhiteInversion) { | ||
return color | ||
} | ||
/* Just black and white inversion. */ | ||
const bkgLum = Luminance(this.clearColor) | ||
if (color === 0xffffff && bkgLum >= 0.8) { | ||
return 0 | ||
} | ||
if (color === 0 && bkgLum <= 0.2) { | ||
return 0xffffff | ||
} | ||
if (!this.options.colorCorrection) { | ||
return color | ||
} | ||
const fgLum = Luminance(color) | ||
const MIN_TARGET_RATIO = 1.5 | ||
const contrast = ContrastRatio(color, this.clearColor) | ||
const diff = contrast >= 1 ? contrast : 1 / contrast | ||
if (diff < MIN_TARGET_RATIO) { | ||
let targetLum | ||
if (bkgLum > 0.5) { | ||
targetLum = bkgLum / 2 | ||
} else { | ||
targetLum = bkgLum * 2 | ||
} | ||
if (targetLum > fgLum) { | ||
color = Lighten(color, targetLum / fgLum) | ||
} else { | ||
color = Darken(color, fgLum / targetLum) | ||
} | ||
} | ||
return color | ||
/** Ensure the color is contrast enough with current background color. | ||
* @param color {number} RGB value. | ||
* @return {number} RGB value to use for rendering. | ||
*/ | ||
_TransformColor(color) { | ||
if (!this.options.colorCorrection && !this.options.blackWhiteInversion) { | ||
return color; | ||
} | ||
/* Just black and white inversion. */ | ||
const bkgLum = Luminance(this.clearColor); | ||
if (color === 0xffffff && bkgLum >= 0.8) { | ||
return 0; | ||
} | ||
if (color === 0 && bkgLum <= 0.2) { | ||
return 0xffffff; | ||
} | ||
if (!this.options.colorCorrection) { | ||
return color; | ||
} | ||
const fgLum = Luminance(color); | ||
const MIN_TARGET_RATIO = 1.5; | ||
const contrast = ContrastRatio(color, this.clearColor); | ||
const diff = contrast >= 1 ? contrast : 1 / contrast; | ||
if (diff < MIN_TARGET_RATIO) { | ||
let targetLum; | ||
if (bkgLum > 0.5) { | ||
targetLum = bkgLum / 2; | ||
} else { | ||
targetLum = bkgLum * 2; | ||
} | ||
if (targetLum > fgLum) { | ||
color = Lighten(color, targetLum / fgLum); | ||
} else { | ||
color = Darken(color, fgLum / targetLum); | ||
} | ||
} | ||
return color; | ||
} | ||
} | ||
DxfViewer.MessageLevel = MessageLevel | ||
DxfViewer.MessageLevel = MessageLevel; | ||
DxfViewer.DefaultOptions = { | ||
canvasWidth: 400, | ||
canvasHeight: 300, | ||
/** Automatically resize canvas when the container is resized. This options utilizes | ||
* ResizeObserver API which is still not fully standardized. The specified canvas size is | ||
* ignored if the option is enabled. | ||
*/ | ||
autoResize: false, | ||
/** Frame buffer clear color. */ | ||
clearColor: new three.Color("#000"), | ||
/** Frame buffer clear color alpha value. */ | ||
clearAlpha: 1.0, | ||
/** Use alpha channel in a framebuffer. */ | ||
canvasAlpha: false, | ||
/** Assume premultiplied alpha in a framebuffer. */ | ||
canvasPremultipliedAlpha: true, | ||
/** Use antialiasing. May degrade performance on poor hardware. */ | ||
antialias: true, | ||
/** Correct entities colors to ensure that they are always visible with the current background | ||
* color. | ||
*/ | ||
colorCorrection: false, | ||
/** Simpler version of colorCorrection - just invert pure white or black entities if they are | ||
* invisible on current background color. | ||
*/ | ||
blackWhiteInversion: true, | ||
/** Size in pixels for rasterized points (dot mark). */ | ||
pointSize: 2, | ||
/** Scene generation options. */ | ||
sceneOptions: DxfScene.DefaultOptions, | ||
/** Retain the simple object representing the parsed DXF - will consume a lot of additional | ||
* memory. | ||
*/ | ||
retainParsedDxf: false, | ||
/** Whether to preserve the buffers until manually cleared or overwritten. */ | ||
preserveDrawingBuffer: false, | ||
/** Encoding to use for decoding DXF file text content. DXF files newer than DXF R2004 (AC1018) | ||
* use UTF-8 encoding. Older files use some code page which is specified in $DWGCODEPAGE header | ||
* variable. Currently parser is implemented in such a way that encoding must be specified | ||
* before the content is parsed so there is no chance to use this variable dynamically. This may | ||
* be a subject for future changes. The specified value should be suitable for passing as | ||
* `TextDecoder` constructor `label` parameter. | ||
*/ | ||
fileEncoding: "utf-8" | ||
} | ||
canvasWidth: 400, | ||
canvasHeight: 300, | ||
/** Automatically resize canvas when the container is resized. This options utilizes | ||
* ResizeObserver API which is still not fully standardized. The specified canvas size is | ||
* ignored if the option is enabled. | ||
*/ | ||
autoResize: false, | ||
/** Frame buffer clear color. */ | ||
clearColor: new three.Color('#000'), | ||
/** Frame buffer clear color alpha value. */ | ||
clearAlpha: 1.0, | ||
/** Use alpha channel in a framebuffer. */ | ||
canvasAlpha: false, | ||
/** Assume premultiplied alpha in a framebuffer. */ | ||
canvasPremultipliedAlpha: true, | ||
/** Use antialiasing. May degrade performance on poor hardware. */ | ||
antialias: true, | ||
/** Correct entities colors to ensure that they are always visible with the current background | ||
* color. | ||
*/ | ||
colorCorrection: false, | ||
/** Simpler version of colorCorrection - just invert pure white or black entities if they are | ||
* invisible on current background color. | ||
*/ | ||
blackWhiteInversion: true, | ||
/** Size in pixels for rasterized points (dot mark). */ | ||
pointSize: 2, | ||
/** Scene generation options. */ | ||
sceneOptions: DxfScene.DefaultOptions, | ||
/** Retain the simple object representing the parsed DXF - will consume a lot of additional | ||
* memory. | ||
*/ | ||
retainParsedDxf: false, | ||
/** Whether to preserve the buffers until manually cleared or overwritten. */ | ||
preserveDrawingBuffer: false, | ||
/** Encoding to use for decoding DXF file text content. DXF files newer than DXF R2004 (AC1018) | ||
* use UTF-8 encoding. Older files use some code page which is specified in $DWGCODEPAGE header | ||
* variable. Currently parser is implemented in such a way that encoding must be specified | ||
* before the content is parsed so there is no chance to use this variable dynamically. This may | ||
* be a subject for future changes. The specified value should be suitable for passing as | ||
* `TextDecoder` constructor `label` parameter. | ||
*/ | ||
fileEncoding: 'utf-8', | ||
}; | ||
DxfViewer.SetupWorker = function () { | ||
new DxfWorker(self, true) | ||
} | ||
new DxfWorker(self, true); | ||
}; | ||
const InstanceType = Object.freeze({ | ||
/** Not instanced. */ | ||
NONE: 0, | ||
/** Full affine transform per instance. */ | ||
FULL: 1, | ||
/** Point instances, 2D-translation vector per instance. */ | ||
POINT: 2, | ||
/** Not instanced. */ | ||
NONE: 0, | ||
/** Full affine transform per instance. */ | ||
FULL: 1, | ||
/** Point instances, 2D-translation vector per instance. */ | ||
POINT: 2, | ||
/** Number of types. */ | ||
MAX: 3 | ||
}) | ||
/** Number of types. */ | ||
MAX: 3, | ||
}); | ||
class Batch { | ||
/** | ||
* @param viewer {DxfViewer} | ||
* @param scene Serialized scene. | ||
* @param batch Serialized scene batch. | ||
*/ | ||
constructor(viewer, scene, batch, blockName) { | ||
this.blockName = blockName | ||
this.viewer = viewer | ||
this.key = batch.key | ||
this.positions = batch.positions | ||
this.position = batch.position | ||
this.name = blockName | ||
this.transformsOffset = batch.transformsOffset | ||
this.verticesOffset = batch.verticesOffset | ||
/** | ||
* @param viewer {DxfViewer} | ||
* @param scene Serialized scene. | ||
* @param batch Serialized scene batch. | ||
*/ | ||
constructor(viewer, scene, batch, blockName) { | ||
this.blockName = blockName; | ||
this.viewer = viewer; | ||
this.key = batch.key; | ||
this.positions = batch.positions; | ||
this.position = batch.position; | ||
this.name = blockName; | ||
this.transformsOffset = batch.transformsOffset; | ||
this.verticesOffset = batch.verticesOffset; | ||
if (batch.hasOwnProperty("verticesOffset")) { | ||
const verticesArray = | ||
new Float32Array(scene.vertices, | ||
batch.verticesOffset * Float32Array.BYTES_PER_ELEMENT, | ||
batch.verticesSize) | ||
if (this.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE || | ||
scene.pointShapeHasDot) { | ||
this.vertices = new three.BufferAttribute(verticesArray, 2) | ||
} | ||
if (this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) { | ||
this.transforms = new three.InstancedBufferAttribute(verticesArray, 2) | ||
} | ||
} | ||
if (batch.hasOwnProperty('verticesOffset')) { | ||
const verticesArray = new Float32Array( | ||
scene.vertices, | ||
batch.verticesOffset * Float32Array.BYTES_PER_ELEMENT, | ||
batch.verticesSize, | ||
); | ||
if ( | ||
this.key.geometryType !== BatchingKey.GeometryType.POINT_INSTANCE || | ||
scene.pointShapeHasDot | ||
) { | ||
this.vertices = new three.BufferAttribute(verticesArray, 2); | ||
} | ||
if (this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) { | ||
this.transforms = new three.InstancedBufferAttribute(verticesArray, 2); | ||
} | ||
} | ||
if (batch.hasOwnProperty("chunks")) { | ||
this.chunks = [] | ||
for (const rawChunk of batch.chunks) { | ||
if (batch.hasOwnProperty('chunks')) { | ||
this.chunks = []; | ||
for (const rawChunk of batch.chunks) { | ||
const verticesArray = new Float32Array( | ||
scene.vertices, | ||
rawChunk.verticesOffset * Float32Array.BYTES_PER_ELEMENT, | ||
rawChunk.verticesSize, | ||
); | ||
const indicesArray = new Uint16Array( | ||
scene.indices, | ||
rawChunk.indicesOffset * Uint16Array.BYTES_PER_ELEMENT, | ||
rawChunk.indicesSize, | ||
); | ||
this.chunks.push({ | ||
vertices: new three.BufferAttribute(verticesArray, 2), | ||
indices: new three.BufferAttribute(indicesArray, 1), | ||
}); | ||
} | ||
} | ||
const verticesArray = | ||
new Float32Array(scene.vertices, | ||
rawChunk.verticesOffset * Float32Array.BYTES_PER_ELEMENT, | ||
rawChunk.verticesSize) | ||
const indicesArray = | ||
new Uint16Array(scene.indices, | ||
rawChunk.indicesOffset * Uint16Array.BYTES_PER_ELEMENT, | ||
rawChunk.indicesSize) | ||
this.chunks.push({ | ||
vertices: new three.BufferAttribute(verticesArray, 2), | ||
indices: new three.BufferAttribute(indicesArray, 1) | ||
}) | ||
} | ||
} | ||
if (batch.hasOwnProperty('transformsOffset')) { | ||
const transformsArray = new Float32Array( | ||
scene.transforms, | ||
batch.transformsOffset * Float32Array.BYTES_PER_ELEMENT, | ||
batch.transformsSize, | ||
); | ||
/* Each transform is 3x2 matrix which is split into two 3D vectors which will occupy two | ||
* attribute slots. | ||
*/ | ||
const buf = new three.InstancedInterleavedBuffer(transformsArray, 6); | ||
this.transforms0 = new three.InterleavedBufferAttribute(buf, 3, 0); | ||
this.transforms1 = new three.InterleavedBufferAttribute(buf, 3, 3); | ||
} | ||
if (batch.hasOwnProperty("transformsOffset")) { | ||
const transformsArray = | ||
new Float32Array(scene.transforms, | ||
batch.transformsOffset * Float32Array.BYTES_PER_ELEMENT, | ||
batch.transformsSize) | ||
/* Each transform is 3x2 matrix which is split into two 3D vectors which will occupy two | ||
* attribute slots. | ||
*/ | ||
const buf = new three.InstancedInterleavedBuffer(transformsArray, 6) | ||
this.transforms0 = new three.InterleavedBufferAttribute(buf, 3, 0) | ||
this.transforms1 = new three.InterleavedBufferAttribute(buf, 3, 3) | ||
} | ||
if ( | ||
this.key.geometryType === BatchingKey.GeometryType.BLOCK_INSTANCE || | ||
this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE | ||
) { | ||
const layer = this.viewer.layers.get(this.key.layerName); | ||
if (layer) { | ||
this.layerColor = layer.color; | ||
} else { | ||
this.layerColor = 0; | ||
} | ||
} | ||
} | ||
if (this.key.geometryType === BatchingKey.GeometryType.BLOCK_INSTANCE || | ||
this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) { | ||
const layer = this.viewer.layers.get(this.key.layerName) | ||
if (layer) { | ||
this.layerColor = layer.color | ||
} else { | ||
this.layerColor = 0 | ||
} | ||
} | ||
GetInstanceType() { | ||
switch (this.key.geometryType) { | ||
case BatchingKey.GeometryType.BLOCK_INSTANCE: | ||
return InstanceType.FULL; | ||
case BatchingKey.GeometryType.POINT_INSTANCE: | ||
return InstanceType.POINT; | ||
default: | ||
return InstanceType.NONE; | ||
} | ||
} | ||
GetInstanceType() { | ||
switch (this.key.geometryType) { | ||
case BatchingKey.GeometryType.BLOCK_INSTANCE: | ||
return InstanceType.FULL | ||
case BatchingKey.GeometryType.POINT_INSTANCE: | ||
return InstanceType.POINT | ||
default: | ||
return InstanceType.NONE | ||
} | ||
/** Create scene objects corresponding to batch data. | ||
* @param instanceBatch {?Batch} Batch with instance transform. Null for non-instanced object. | ||
*/ | ||
*CreateObjects(instanceBatch = null, bypassBlock) { | ||
if (bypassBlock) { | ||
yield* this._CreateObjects(instanceBatch); | ||
return; | ||
} | ||
/** Create scene objects corresponding to batch data. | ||
* @param instanceBatch {?Batch} Batch with instance transform. Null for non-instanced object. | ||
*/ | ||
*CreateObjects(instanceBatch = null, bypassBlock) { | ||
if (bypassBlock) { | ||
yield* this._CreateObjects(instanceBatch) | ||
return | ||
} | ||
if (this.key.geometryType === BatchingKey.GeometryType.BLOCK_INSTANCE || | ||
this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) { | ||
if (instanceBatch !== null) { | ||
throw new Error("Unexpected instance batch specified for instance batch") | ||
} | ||
yield* this._CreateBlockInstanceObjects() | ||
return | ||
} | ||
yield* this._CreateObjects(instanceBatch) | ||
if ( | ||
this.key.geometryType === BatchingKey.GeometryType.BLOCK_INSTANCE || | ||
this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE | ||
) { | ||
if (instanceBatch !== null) { | ||
throw new Error('Unexpected instance batch specified for instance batch'); | ||
} | ||
yield* this._CreateBlockInstanceObjects(); | ||
return; | ||
} | ||
yield* this._CreateObjects(instanceBatch); | ||
} | ||
*_CreateObjects(instanceBatch) { | ||
const color = instanceBatch ? | ||
instanceBatch._GetInstanceColor(this.key.color) : this.key.color | ||
*_CreateObjects(instanceBatch) { | ||
const color = instanceBatch ? instanceBatch._GetInstanceColor(this.key.color) : this.key.color; | ||
//XXX line type | ||
const materialFactory = | ||
this.key.geometryType === BatchingKey.GeometryType.POINTS || | ||
this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE ? | ||
this.viewer._GetSimplePointMaterial : this.viewer._GetSimpleColorMaterial | ||
//XXX line type | ||
const materialFactory = | ||
this.key.geometryType === BatchingKey.GeometryType.POINTS || | ||
this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE | ||
? this.viewer._GetSimplePointMaterial | ||
: this.viewer._GetSimpleColorMaterial; | ||
const material = materialFactory.call(this.viewer, this.viewer._TransformColor(color), | ||
instanceBatch?.GetInstanceType() ?? InstanceType.NONE) | ||
const material = materialFactory.call( | ||
this.viewer, | ||
this.viewer._TransformColor(color), | ||
instanceBatch?.GetInstanceType() ?? InstanceType.NONE, | ||
); | ||
let objConstructor | ||
switch (this.key.geometryType) { | ||
case BatchingKey.GeometryType.POINTS: | ||
/* This method also called for creating dots for shaped point instances. */ | ||
case BatchingKey.GeometryType.POINT_INSTANCE: | ||
objConstructor = three.Points | ||
break | ||
case BatchingKey.GeometryType.LINES: | ||
case BatchingKey.GeometryType.INDEXED_LINES: | ||
objConstructor = three.LineSegments | ||
break | ||
case BatchingKey.GeometryType.TRIANGLES: | ||
case BatchingKey.GeometryType.INDEXED_TRIANGLES: | ||
objConstructor = three.Mesh | ||
break | ||
default: | ||
throw new Error("Unexpected geometry type:" + this.key.geometryType) | ||
} | ||
let objConstructor; | ||
switch (this.key.geometryType) { | ||
case BatchingKey.GeometryType.POINTS: | ||
/* This method also called for creating dots for shaped point instances. */ | ||
case BatchingKey.GeometryType.POINT_INSTANCE: | ||
objConstructor = three.Points; | ||
break; | ||
case BatchingKey.GeometryType.LINES: | ||
case BatchingKey.GeometryType.INDEXED_LINES: | ||
objConstructor = three.LineSegments; | ||
break; | ||
case BatchingKey.GeometryType.TRIANGLES: | ||
case BatchingKey.GeometryType.INDEXED_TRIANGLES: | ||
objConstructor = three.Mesh; | ||
break; | ||
default: | ||
throw new Error('Unexpected geometry type:' + this.key.geometryType); | ||
} | ||
function CreateObject(vertices, indices) { | ||
const geometry = instanceBatch ? | ||
new three.InstancedBufferGeometry() : new three.BufferGeometry() | ||
geometry.setAttribute("position", vertices) | ||
instanceBatch?._SetInstanceTransformAttribute(geometry) | ||
if (indices) { | ||
geometry.setIndex(indices) | ||
} | ||
const obj = new objConstructor(geometry, material) | ||
obj.frustumCulled = false | ||
obj.matrixAutoUpdate = false | ||
obj.vertices = vertices | ||
return obj | ||
} | ||
function CreateObject(vertices, indices) { | ||
const geometry = instanceBatch | ||
? new three.InstancedBufferGeometry() | ||
: new three.BufferGeometry(); | ||
geometry.setAttribute('position', vertices); | ||
instanceBatch?._SetInstanceTransformAttribute(geometry); | ||
if (indices) { | ||
geometry.setIndex(indices); | ||
} | ||
const obj = new objConstructor(geometry, material); | ||
obj.frustumCulled = false; | ||
obj.matrixAutoUpdate = false; | ||
obj.vertices = vertices; | ||
return obj; | ||
} | ||
if (this.chunks) { | ||
for (const chunk of this.chunks) { | ||
yield CreateObject(chunk.vertices, chunk.indices) | ||
} | ||
} else { | ||
yield CreateObject(this.vertices, null) | ||
} | ||
if (this.chunks) { | ||
for (const chunk of this.chunks) { | ||
yield CreateObject(chunk.vertices, chunk.indices); | ||
} | ||
} else { | ||
yield CreateObject(this.vertices, null); | ||
} | ||
} | ||
/** | ||
* @param geometry {InstancedBufferGeometry} | ||
*/ | ||
_SetInstanceTransformAttribute(geometry) { | ||
if (!geometry.isInstancedBufferGeometry) { | ||
throw new Error("InstancedBufferGeometry expected") | ||
} | ||
if (this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) { | ||
geometry.setAttribute("instanceTransform", this.transforms) | ||
} else { | ||
geometry.setAttribute("instanceTransform0", this.transforms0) | ||
geometry.setAttribute("instanceTransform1", this.transforms1) | ||
} | ||
/** | ||
* @param geometry {InstancedBufferGeometry} | ||
*/ | ||
_SetInstanceTransformAttribute(geometry) { | ||
if (!geometry.isInstancedBufferGeometry) { | ||
throw new Error('InstancedBufferGeometry expected'); | ||
} | ||
if (this.key.geometryType === BatchingKey.GeometryType.POINT_INSTANCE) { | ||
geometry.setAttribute('instanceTransform', this.transforms); | ||
} else { | ||
geometry.setAttribute('instanceTransform0', this.transforms0); | ||
geometry.setAttribute('instanceTransform1', this.transforms1); | ||
} | ||
} | ||
*_CreateBlockInstanceObjects() { | ||
const block = this.viewer.blocks.get(this.key.blockName) | ||
if (!block) { | ||
return | ||
} | ||
for (const batch of block.batches) { | ||
yield* batch.CreateObjects(this) | ||
} | ||
if (this.hasOwnProperty("vertices")) { | ||
/* Dots for point shapes. */ | ||
yield* this._CreateObjects() | ||
} | ||
*_CreateBlockInstanceObjects() { | ||
const block = this.viewer.blocks.get(this.key.blockName); | ||
if (!block) { | ||
return; | ||
} | ||
for (const batch of block.batches) { | ||
yield* batch.CreateObjects(this); | ||
} | ||
if (this.hasOwnProperty('vertices')) { | ||
/* Dots for point shapes. */ | ||
yield* this._CreateObjects(); | ||
} | ||
} | ||
/** | ||
* @param defColor {number} Color value for block definition batch. | ||
* @return {number} RGB color value for a block instance. | ||
*/ | ||
_GetInstanceColor(defColor) { | ||
if (defColor === ColorCode.BY_BLOCK) { | ||
return this.key.color | ||
} else if (defColor === ColorCode.BY_LAYER) { | ||
return this.layerColor | ||
} else { | ||
return defColor | ||
} | ||
/** | ||
* @param defColor {number} Color value for block definition batch. | ||
* @return {number} RGB color value for a block instance. | ||
*/ | ||
_GetInstanceColor(defColor) { | ||
if (defColor === ColorCode.BY_BLOCK) { | ||
return this.key.color; | ||
} else if (defColor === ColorCode.BY_LAYER) { | ||
return this.layerColor; | ||
} else { | ||
return defColor; | ||
} | ||
} | ||
} | ||
class Layer { | ||
constructor(name, displayName, color) { | ||
this.name = name | ||
this.displayName = displayName | ||
this.color = color | ||
this.objects = [] | ||
} | ||
constructor(name, displayName, color) { | ||
this.name = name; | ||
this.displayName = displayName; | ||
this.color = color; | ||
this.objects = []; | ||
} | ||
PushObject(obj) { | ||
this.objects.push(obj) | ||
} | ||
PushObject(obj) { | ||
this.objects.push(obj); | ||
} | ||
Dispose() { | ||
for (const obj of this.objects) { | ||
obj.geometry.dispose() | ||
} | ||
this.objects = null | ||
Dispose() { | ||
for (const obj of this.objects) { | ||
obj.geometry.dispose(); | ||
} | ||
this.objects = null; | ||
} | ||
} | ||
class Block { | ||
constructor() { | ||
this.batches = [] | ||
} | ||
constructor() { | ||
this.batches = []; | ||
} | ||
/** @param batch {Batch} */ | ||
PushBatch(batch) { | ||
this.batches.push(batch) | ||
} | ||
/** @param batch {Batch} */ | ||
PushBatch(batch) { | ||
this.batches.push(batch); | ||
} | ||
} | ||
/** Custom viewer event names are prefixed with this string. */ | ||
const EVENT_NAME_PREFIX = "__dxf_" | ||
const EVENT_NAME_PREFIX = '__dxf_'; | ||
/** Transform sRGB color component to linear color space. */ | ||
function LinearColor(c) { | ||
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4) | ||
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); | ||
} | ||
@@ -1255,3 +1292,3 @@ | ||
function SRgbColor(c) { | ||
return c < 0.003 ? c * 12.92 : Math.pow(c, 1 / 2.4) * 1.055 - 0.055 | ||
return c < 0.003 ? c * 12.92 : Math.pow(c, 1 / 2.4) * 1.055 - 0.055; | ||
} | ||
@@ -1265,7 +1302,7 @@ | ||
function Luminance(color) { | ||
const r = LinearColor(((color & 0xff0000) >>> 16) / 255) | ||
const g = LinearColor(((color & 0xff00) >>> 8) / 255) | ||
const b = LinearColor((color & 0xff) / 255) | ||
const r = LinearColor(((color & 0xff0000) >>> 16) / 255); | ||
const g = LinearColor(((color & 0xff00) >>> 8) / 255); | ||
const b = LinearColor((color & 0xff) / 255); | ||
return r * 0.2126 + g * 0.7152 + b * 0.0722 | ||
return r * 0.2126 + g * 0.7152 + b * 0.0722; | ||
} | ||
@@ -1282,88 +1319,90 @@ | ||
function ContrastRatio(c1, c2) { | ||
return (Luminance(c1) + 0.05) / (Luminance(c2) + 0.05) | ||
return (Luminance(c1) + 0.05) / (Luminance(c2) + 0.05); | ||
} | ||
function HlsToRgb({h, l, s}) { | ||
let r, g, b | ||
if (s === 0) { | ||
/* Achromatic */ | ||
r = g = b = l | ||
} else { | ||
function hue2rgb(p, q, t) { | ||
if (t < 0) { | ||
t += 1 | ||
} | ||
if (t > 1) { | ||
t -= 1 | ||
} | ||
if (t < 1/6) { | ||
return p + (q - p) * 6 * t | ||
} | ||
if (t < 1/2) { | ||
return q | ||
} | ||
if (t < 2/3) { | ||
return p + (q - p) * (2/3 - t) * 6 | ||
} | ||
return p | ||
} | ||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s | ||
const p = 2 * l - q | ||
r = hue2rgb(p, q, h + 1/3) | ||
g = hue2rgb(p, q, h) | ||
b = hue2rgb(p, q, h - 1/3) | ||
function HlsToRgb({ h, l, s }) { | ||
let r, g, b; | ||
if (s === 0) { | ||
/* Achromatic */ | ||
r = g = b = l; | ||
} else { | ||
function hue2rgb(p, q, t) { | ||
if (t < 0) { | ||
t += 1; | ||
} | ||
if (t > 1) { | ||
t -= 1; | ||
} | ||
if (t < 1 / 6) { | ||
return p + (q - p) * 6 * t; | ||
} | ||
if (t < 1 / 2) { | ||
return q; | ||
} | ||
if (t < 2 / 3) { | ||
return p + (q - p) * (2 / 3 - t) * 6; | ||
} | ||
return p; | ||
} | ||
return (Math.min(Math.floor(SRgbColor(r) * 256), 255) << 16) | | ||
(Math.min(Math.floor(SRgbColor(g) * 256), 255) << 8) | | ||
Math.min(Math.floor(SRgbColor(b) * 256), 255) | ||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s; | ||
const p = 2 * l - q; | ||
r = hue2rgb(p, q, h + 1 / 3); | ||
g = hue2rgb(p, q, h); | ||
b = hue2rgb(p, q, h - 1 / 3); | ||
} | ||
return ( | ||
(Math.min(Math.floor(SRgbColor(r) * 256), 255) << 16) | | ||
(Math.min(Math.floor(SRgbColor(g) * 256), 255) << 8) | | ||
Math.min(Math.floor(SRgbColor(b) * 256), 255) | ||
); | ||
} | ||
function RgbToHls(color) { | ||
const r = LinearColor(((color & 0xff0000) >>> 16) / 255) | ||
const g = LinearColor(((color & 0xff00) >>> 8) / 255) | ||
const b = LinearColor((color & 0xff) / 255) | ||
const r = LinearColor(((color & 0xff0000) >>> 16) / 255); | ||
const g = LinearColor(((color & 0xff00) >>> 8) / 255); | ||
const b = LinearColor((color & 0xff) / 255); | ||
const max = Math.max(r, g, b) | ||
const min = Math.min(r, g, b) | ||
let h, s | ||
const l = (max + min) / 2 | ||
const max = Math.max(r, g, b); | ||
const min = Math.min(r, g, b); | ||
let h, s; | ||
const l = (max + min) / 2; | ||
if (max === min) { | ||
/* Achromatic */ | ||
h = s = 0 | ||
} else { | ||
const d = max - min | ||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min) | ||
switch (max) { | ||
case r: | ||
h = (g - b) / d + (g < b ? 6 : 0) | ||
break; | ||
case g: | ||
h = (b - r) / d + 2 | ||
break | ||
case b: | ||
h = (r - g) / d + 4 | ||
break | ||
} | ||
h /= 6 | ||
if (max === min) { | ||
/* Achromatic */ | ||
h = s = 0; | ||
} else { | ||
const d = max - min; | ||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | ||
switch (max) { | ||
case r: | ||
h = (g - b) / d + (g < b ? 6 : 0); | ||
break; | ||
case g: | ||
h = (b - r) / d + 2; | ||
break; | ||
case b: | ||
h = (r - g) / d + 4; | ||
break; | ||
} | ||
h /= 6; | ||
} | ||
return {h, l, s} | ||
return { h, l, s }; | ||
} | ||
function Lighten(color, factor) { | ||
const hls = RgbToHls(color) | ||
hls.l *= factor | ||
if (hls.l > 1) { | ||
hls.l = 1 | ||
} | ||
return HlsToRgb(hls) | ||
const hls = RgbToHls(color); | ||
hls.l *= factor; | ||
if (hls.l > 1) { | ||
hls.l = 1; | ||
} | ||
return HlsToRgb(hls); | ||
} | ||
function Darken(color, factor) { | ||
const hls = RgbToHls(color) | ||
hls.l /= factor | ||
return HlsToRgb(hls) | ||
const hls = RgbToHls(color); | ||
hls.l /= factor; | ||
return HlsToRgb(hls); | ||
} |
19547
870397