Merge pull request #777 from specklesystems/alex/viewer-redux

Alex/viewer redux
This commit is contained in:
Dimitrie Stefanescu
2022-06-02 12:06:56 +01:00
committed by GitHub
51 changed files with 18753 additions and 2870 deletions
+1 -1
View File
@@ -39,4 +39,4 @@ events.json
!.yarn/sdks
!.yarn/versions
*yarn-error.log
*yarn-error.log
@@ -8,7 +8,7 @@
</div>
</template>
<script>
import { Viewer } from '@speckle/viewer'
import { Viewer, DefaultViewerParams } from '@speckle/viewer'
import throttle from 'lodash/throttle'
export default {
@@ -41,7 +41,8 @@ export default {
renderDomElement.id = 'renderer'
}
if (!window.__viewer) {
window.__viewer = new Viewer({ container: renderDomElement, showStats: false })
window.__viewer = new Viewer(renderDomElement, DefaultViewerParams)
await window.__viewer.init()
}
this.domElement = renderDomElement
+15 -3
View File
@@ -8,15 +8,27 @@
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="app">
<div id="renderer" />
<!-- <div id="app">
<div class="container">
<div class="row">
<div class="twelve columns h-96">
<div id="renderer" />
<input
id="objectUrlInput"
class="input"
type="text"
name="objectId"
placeholder="Object Url"
style="width: 49%"
value="https://latest.speckle.dev/streams/010b3af4c3/objects/a401baf38fe5809d0eb9d3c902a36e8f"
/>
<button id="loadButton" class="button" onclick="loadData()" style="width: 29%">
Load Object URL
</button>
</div>
</div>
</div>
</div>
</div> -->
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Binary file not shown.
+186
View File
@@ -0,0 +1,186 @@
import { Viewer, IViewer } from '@speckle/viewer'
import SpeckleLineMaterial from '@speckle/viewer/dist/modules/materials/SpeckleLineMaterial'
import { Object3D } from '@speckle/viewer/node_modules/@types/three'
import { Pane } from 'tweakpane'
import UrlHelper from './UrlHelper'
export default class Sandbox {
private viewer: IViewer
private pane: Pane
private tabs
public static urlParams = {
url: 'https://latest.speckle.dev/streams/010b3af4c3/objects/a401baf38fe5809d0eb9d3c902a36e8f'
}
public static sceneParams = {
worldSize: { x: 0, y: 0, z: 0 },
worldOrigin: { x: 0, y: 0, z: 0 },
useRTE: false,
thickLines: true,
pixelThreshold: 0.5,
exposure: 0.4,
tonemapping: 'Linear'
}
public constructor(viewer: Viewer) {
this.viewer = viewer
this.pane = new Pane({ title: 'Sandbox', expanded: true })
this.pane['containerElem_'].style.width = '300px'
const t = `matrix(1.2, 0, 0, 1.2, -25, 16)`
this.pane['containerElem_'].style.transform = t
this.tabs = this.pane.addTab({
pages: [{ title: 'General' }, { title: 'Scene' }]
})
Sandbox.sceneParams.useRTE = viewer.RTE
Sandbox.sceneParams.thickLines = viewer.thickLines
}
public refresh() {
this.pane.refresh()
}
public makeGenericUI() {
this.tabs.pages[0].addInput(Sandbox.urlParams, 'url', {
title: 'url'
})
const loadButton = this.tabs.pages[0].addButton({
title: 'Load Url'
})
loadButton.on('click', () => {
this.loadUrl(Sandbox.urlParams.url)
})
const clearButton = this.tabs.pages[0].addButton({
title: 'Clear All'
})
clearButton.on('click', () => {
this.viewer.unloadAll()
})
this.tabs.pages[0].addSeparator()
const toggleSectionBox = this.tabs.pages[0].addButton({
title: 'Toggle Section Box'
})
toggleSectionBox.on('click', () => {
this.viewer.toggleSectionBox()
})
const toggleProjection = this.tabs.pages[0].addButton({
title: 'Toggle Projection'
})
toggleProjection.on('click', () => {
this.viewer.toggleCameraProjection()
})
const zoomExtents = this.tabs.pages[0].addButton({
title: 'Zoom Extents'
})
zoomExtents.on('click', () => {
this.viewer.zoomExtents(undefined, true)
})
}
makeSceneUI() {
const worldFolder = this.tabs.pages[1].addFolder({
title: 'World',
expanded: true
})
worldFolder.addInput(Sandbox.sceneParams.worldSize, 'x', {
disabled: true,
label: 'Size-x',
step: 0.00000001
})
worldFolder.addInput(Sandbox.sceneParams.worldSize, 'y', {
disabled: true,
label: 'Size-y',
step: 0.00000001
})
worldFolder.addInput(Sandbox.sceneParams.worldSize, 'z', {
disabled: true,
label: 'Size-z',
step: 0.00000001
})
worldFolder.addSeparator()
worldFolder.addInput(Sandbox.sceneParams.worldOrigin, 'x', {
disabled: true,
label: 'Origin-x'
})
worldFolder.addInput(Sandbox.sceneParams.worldOrigin, 'y', {
disabled: true,
label: 'Origin-y'
})
worldFolder.addInput(Sandbox.sceneParams.worldOrigin, 'z', {
disabled: true,
label: 'Origin-z'
})
worldFolder
.addInput(Sandbox.sceneParams, 'useRTE', {
label: 'RTE'
})
.on('change', () => {
this.viewer.RTE = Sandbox.sceneParams.useRTE
})
worldFolder
.addInput(Sandbox.sceneParams, 'thickLines', {
label: 'Thick Lines'
})
.on('change', () => {
this.viewer.thickLines = Sandbox.sceneParams.thickLines
})
worldFolder
.addInput(Sandbox.sceneParams, 'pixelThreshold', {
min: 0,
max: 5
})
.on('change', () => {
this.viewer.scene.traverse((object: Object3D) => {
if (object.type === 'Line2') {
;(object.material as SpeckleLineMaterial).pixelThreshold =
Sandbox.sceneParams.pixelThreshold
}
})
})
this.tabs.pages[1].addSeparator()
const postFolder = this.tabs.pages[1].addFolder({
title: 'Post',
expanded: true
})
postFolder
.addInput(Sandbox.sceneParams, 'exposure', {
min: 0,
max: 1
})
.on('change', () => {
this.viewer.renderer.toneMappingExposure = Sandbox.sceneParams.exposure
})
postFolder
.addInput(Sandbox.sceneParams, 'tonemapping', {
options: {
Linear: 1,
ACES: 4
}
})
.on('change', () => {
this.viewer.renderer.toneMapping = Sandbox.sceneParams.tonemapping
})
}
public async loadUrl(url: string) {
const objUrls = await UrlHelper.getResourceUrls(url)
for (const url of objUrls) {
console.log(`Loading ${url}`)
await this.viewer.loadObject(url)
}
localStorage.setItem('last-load-url', url)
}
}
+81
View File
@@ -0,0 +1,81 @@
interface CommitReferencedObjectUrl {
origin: string
streamId: string
commitId: string
}
export default class UrlHelper {
static async getResourceUrls(url: string): Promise<string[]> {
const parsed = new URL(url)
const streamId = url.split('/streams/')[1].substring(0, 10)
const objsUrls = []
// supports commit based urls
if (url.includes('commits')) {
const commitId = url.split('/commits/')[1].substring(0, 10)
const objUrl = await this.getCommitReferencedObjectUrl({
origin: parsed.origin,
streamId,
commitId
})
objsUrls.push(objUrl)
}
// object based urls
if (url.includes('objects')) objsUrls.push(url)
// supports urls that include overlay queries
// e.g., https://speckle.xyz/streams/a632e7a784/objects/457c45feffa6f954572e5e86fb6d4f25?overlay=cf8dc76247,f5adc1d991b3dceb4b5ad6b50f919a0e
if (url.includes('overlay=')) {
const searchParams = new URLSearchParams(parsed.search)
const resIds = searchParams.get('overlay')?.split(',')
if (resIds !== undefined) {
for (const resId of resIds) {
if (resId.length === 10) {
objsUrls.push(
await this.getCommitReferencedObjectUrl({
origin: parsed.origin,
streamId,
commitId: resId
} as CommitReferencedObjectUrl)
)
} else {
objsUrls.push(`${parsed.origin}/streams/${streamId}/objects/${resId}`)
}
}
}
}
return objsUrls
}
private static async getCommitReferencedObjectUrl(ref: CommitReferencedObjectUrl) {
const headers: { 'Content-Type': string; Authorization: string } = {
'Content-Type': 'application/json',
Authorization: ''
}
const authToken = localStorage.getItem('AuthToken')
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`
}
const res = await fetch(`${ref.origin}/graphql`, {
method: 'POST',
headers,
body: JSON.stringify({
query: `
query Stream($streamId: String!, $commitId: String!) {
stream(id: $streamId) {
commit(id: $commitId) {
referencedObject
}
}
}
`,
variables: { streamId: ref.streamId, commitId: ref.commitId }
})
})
const { data } = await res.json()
return `${ref.origin}/streams/${ref.streamId}/objects/${data.stream.commit.referencedObject}`
}
}
+26 -27
View File
@@ -1,42 +1,41 @@
import { Pane } from 'tweakpane'
import { Viewer } from '@speckle/viewer'
import './style.css'
import { Viewer, DefaultViewerParams } from '@speckle/viewer'
const container = document.querySelector<HTMLDivElement>('#renderer')
import './style.css'
import Sandbox from './Sandbox'
const container = document.querySelector<HTMLElement>('#renderer')
if (!container) {
throw new Error("Couldn't find #app container!")
}
// Viewer setup
const viewer = new Viewer({
container,
showStats: true
})
const params = DefaultViewerParams
// params.environmentSrc =
// 'https://speckle-xyz-assets.ams3.digitaloceanspaces.com/studio010.hdr'
// 'http://localhost:3033/sample-hdri.exr'
const viewer = new Viewer(container, params)
await viewer.init()
const sandbox = new Sandbox(viewer)
window.addEventListener('load', () => {
viewer.onWindowResize()
})
// Tweakpane setup
const PARAMS = {
factor: 123,
title: 'hello',
color: '#ff0055'
}
const pane = new Pane()
pane.addInput(PARAMS, 'factor')
pane.addInput(PARAMS, 'title')
pane.addInput(PARAMS, 'color')
// Load demo object
viewer.loadObject(
'https://speckle.xyz/streams/9217731fc1/objects/111a9dc2ed245f26a6584354b11b083f'
)
viewer.on<{ progress: number; id: string; url: string }>('load-progress', (a) => {
viewer.on('load-progress', (a: { progress: number; id: string; url: string }) => {
if (a.progress >= 1) {
viewer.onWindowResize()
}
})
viewer.on('load-complete', () => {
Object.assign(Sandbox.sceneParams.worldSize, viewer.worldSize)
Object.assign(Sandbox.sceneParams.worldOrigin, viewer.worldOrigin)
sandbox.refresh()
})
sandbox.makeGenericUI()
sandbox.makeSceneUI()
// Load demo object
sandbox.loadUrl('https://latest.speckle.dev/streams/3ed8357f29/commits/b21fb0dcf7')
+27
View File
@@ -12,3 +12,30 @@
height: 100%;
width: 100%;
}
.button {
border: 0;
line-height: 1.5;
padding: 0 20px;
font-size: 1rem;
text-align: center;
color: #fff;
text-shadow: 1px 1px 1px #000;
border-radius: 2px;
background-color: rgb(129, 129, 129);
background-image: linear-gradient(
to top left,
rgba(0, 0, 0, 0.2),
rgba(0, 0, 0, 0.2) 30%,
rgba(0, 0, 0, 0)
);
box-shadow: inset 2px 2px 3px rgba(255, 255, 255, 0.6),
inset -2px -2px 3px rgba(0, 0, 0, 0.6);
}
.input {
margin-bottom: 5px;
left: 0px;
border-radius: 0.1rem;
border: 4px solid rgb(129, 129, 129);
}
@@ -1,22 +0,0 @@
/**
* In the future we can integrate these types into the actual viewer package
*/
declare module '@speckle/viewer' {
declare class Viewer {
constructor(params: {
container: Node
postprocessing?: boolean = false
reflections?: boolean = true
showStats?: boolean = false
})
async loadObject(url: string, token?: string, enableCaching? = true): Promise<void>
onWindowResize(): void
on<A1 = unknown, A2 = unknown, A3 = unknown>(
event: string,
callback: (arg1: A1, arg2: A2, arg3: A3) => void
)
}
export { Viewer }
}
+7350
View File
File diff suppressed because one or more lines are too long
+7369
View File
File diff suppressed because one or more lines are too long
-12
View File
@@ -1,12 +0,0 @@
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": "3"
}
]
],
"ignore": ["node_modules/**/*"]
}
-1
View File
@@ -1 +0,0 @@
since 2019
+13 -1
View File
@@ -14,7 +14,19 @@ const config = {
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }]
},
ignorePatterns: ['dist2', 'example/speckleviewer.web.js']
ignorePatterns: ['dist2', 'example/speckleviewer.web.js'],
overrides: [
{
files: '*.ts',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier'
],
parser: '@typescript-eslint/parser'
}
]
}
module.exports = config
+15 -21
View File
@@ -10,6 +10,12 @@
},
"main": "dist/speckleviewer.js",
"module": "dist/speckleviewer.esm.js",
"exports": {
".": "./dist/speckleviewer.esm.js",
"./dist/assets/*": "./dist/assets/*",
"./assets/*": "./dist/assets/*"
},
"types": "./dist/index.d.ts",
"sourceType": "module",
"files": [
"dist"
@@ -37,41 +43,29 @@
"dependencies": {
"@speckle/objectloader": "workspace:^",
"camera-controls": "^1.33.1",
"core-js": "^3.21.1",
"hold-event": "^0.1.0",
"lodash.debounce": "^4.0.8",
"rainbowvis.js": "^1.0.1",
"regenerator-runtime": "^0.13.7",
"three": "^0.136.0"
"three": "^0.140.0"
},
"devDependencies": {
"@babel/cli": "7.15.7",
"@babel/core": "7.15.8",
"@babel/eslint-parser": "^7.15.8",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-private-methods": "^7.14.5",
"@babel/plugin-transform-classes": "^7.16.0",
"@babel/preset-env": "7.15.8",
"@babel/preset-react": "7.14.5",
"@babel/preset-typescript": "7.15.0",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-commonjs": "^21.0.3",
"@rollup/plugin-node-resolve": "^13.1.3",
"babel-jest": "27.2.5",
"babel-loader": "^8.0.0-beta.4",
"babel-plugin-add-module-exports": "1.0.4",
"babel-plugin-transform-class-properties": "6.24.1",
"browserslist": "^4.20.2",
"@rollup/plugin-typescript": "^8.3.2",
"@types/three": "^0.136.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"http-server": "^14.1.0",
"jest": "27.2.5",
"mocha": "^9.1.2",
"prettier": "^2.5.1",
"rollup": "^2.70.1",
"rollup-plugin-delete": "^2.0.0",
"rollup-plugin-rebase": "^4.1.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-terser": "^7.0.2",
"yargs": "^17.2.1"
"rollup-plugin-typescript2": "^0.31.2",
"typescript": "^4.5.4"
},
"gitHead": "5627e490f9a3ecadf19cc4686ad15f344d9ad2d3"
}
+12 -2
View File
@@ -1,9 +1,11 @@
import commonjs from '@rollup/plugin-commonjs'
import nodeResolve from '@rollup/plugin-node-resolve'
import { terser } from 'rollup-plugin-terser'
import { babel } from '@rollup/plugin-babel'
import clean from 'rollup-plugin-delete'
import pkg from './package.json'
import typescript2 from 'rollup-plugin-typescript2'
import rebasePlugin from 'rollup-plugin-rebase'
import copyPlugin from 'rollup-plugin-copy'
const isProd = process.env.NODE_ENV === 'production'
const isExample = !!process.env.EXAMPLE_BUILD
@@ -36,6 +38,15 @@ function buildConfig(isWebBuild = false) {
])
],
plugins: [
rebasePlugin({ keepName: true }),
copyPlugin({
targets: [{ src: './always-bundled-assets/**/*', dest: 'dist/assets' }]
}),
typescript2({
tsconfigOverride: {
sourceMap: sourcemap
}
}),
...(isWebBuild
? [
// Bundling in all deps in web build
@@ -46,7 +57,6 @@ function buildConfig(isWebBuild = false) {
// Cleaning dir only inside dist
clean({ targets: 'dist/*' })
]),
babel({ babelHelpers: 'bundled' }),
...(isProd ? [terser()] : [])
],
external: isWebBuild
+60
View File
@@ -0,0 +1,60 @@
import sampleHdri from './assets/sample-hdri.png'
export interface ViewerParams {
postprocessing: boolean
reflections: boolean
showStats: boolean
environmentSrc: Asset | string
}
export enum AssetType {
TEXTURE_8BPP = 'png', // For now
TEXTURE_HDR = 'hdr',
TEXTURE_EXR = 'exr'
}
export interface Asset {
src: string
type: AssetType
}
/**
* The default HDRI the viewer uses is actually a true HDR image (.exr),
* specified by the explicit TEXTURE_EXR
*
* We do this because bundling an actual .exr or .hdr image format would require
* anybody consuming the viewer to make adjustments to their build config, to enable
* its import.
*
* Three.js doesn't mind the extension of the asset you load, so an .exr hidden behind
* a .png will work just fine.
*/
export const DefaultViewerParams: ViewerParams = {
postprocessing: false,
reflections: true,
showStats: true,
environmentSrc: {
src: sampleHdri,
type: AssetType.TEXTURE_EXR
}
}
/**
* Carried over from the old Viewer. To be extended/changed
*/
export interface IViewer {
init(): Promise<void>
toggleSectionBox(): void
sectionBoxOff(): void
sectionBoxOn(): void
zoomExtents(fit: number, transition: boolean): void
toggleCameraProjection(): void
loadObject(url: string, token?: string, enableCaching?: boolean): Promise<void>
cancelLoad(url: string, unload?: boolean): Promise<void>
unloadObject(url: string): Promise<void>
unloadAll(): Promise<void>
applyFilter(filter: unknown): Promise<unknown>
getObjectsProperties(includeAll?: boolean): unknown
dispose(): void
}
-97
View File
@@ -1,97 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Speckle Viewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,700&display=swap"
rel="stylesheet"
/>
<link
href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css"
integrity="sha512-EZLkOqwILORob+p0BXZc+Vm3RgJBOe1Iq/0fiI7r/wJgzOFZMlsqTa29UEl6v6U6gsV4uIpsNZoV32YZqrCRCQ=="
crossorigin="anonymous"
/>
<style type="text/css">
#renderer {
height: 700px;
width: 100%;
}
</style>
</head>
<body style="background: #e4e4e4">
<div class="container">
<div class="row" style="padding-top: 20px">
<div class="twelve columns">
<h3>Viewer</h3>
<h5>Controls summary:</h5>
<p class="text-sm">
Click an object to select it. Double click it to focus on it. Press `esc` to
clear the selection. Press `shift-s` to toggle a section plane. Press `s`
while the section plane is active to toggle its control mode. Double click
anywhere outside an object to zoom extents to the entire scene.
</p>
</div>
<div class="twelve columns">
<button onclick="v.postprocessing = !v.postprocessing">
Postprocessing Toggle
</button>
<button onclick="v.interactions.zoomExtents()">Zoom Extents</button>
<button onclick="v.toggleSectionBox()">Toggle Section Box</button>
<button onclick="v.interactions.rotateCamera(undefined, undefined, false)">
Rotate
</button>
<button onclick="viewerScreenshot()">Screenshot</button>
</div>
<div class="twelve columns">
<input
id="objectUrlInput"
type="text"
name="objectId"
placeholder="Object Url"
style="width: 49%"
value="https://latest.speckle.dev/streams/010b3af4c3/objects/a401baf38fe5809d0eb9d3c902a36e8f"
/>
<button class="" onclick="loadData()" style="width: 49%">
Load Object URL
</button>
</div>
<div class="twelve columns">
<button onclick="v.unloadAll()">Dispose Everything</button>
View:
<button onclick="v.toggleCameraProjection()">Ortho/Perspective</button>
<button onclick="v.interactions.rotateTo()">3D</button>
<button onclick="v.interactions.rotateTo('top')">Top</button>
<button onclick="v.interactions.rotateTo('front')">Front</button>
<button onclick="v.interactions.rotateTo('back')">Back</button>
<button onclick="v.interactions.rotateTo('left')">Left</button>
<button onclick="v.interactions.rotateTo('right')">Right</button>
</div>
<div class="twelve columns">
Used Memory (MB):
<span id="info-mem">-</span>
/ LoadProgress:
<span id="info-progress">-</span>
/ ViewerBusy:
<span id="info-busy">idle</span>
/ Draw Calls:
<span id="info-draws">-</span>
</div>
</div>
</div>
<div class="row">
<div class="twelve columns">
<div id="renderer"></div>
</div>
</div>
</body>
</html>
Binary file not shown.
@@ -0,0 +1,2 @@
export type Maybe<T> = T | null
export type Optional<T> = T | undefined
+3 -6
View File
@@ -1,8 +1,5 @@
// POLYFILLS
import 'core-js'
import 'regenerator-runtime/runtime'
import Viewer from './modules/Viewer'
import { Viewer } from './modules/Viewer'
import Converter from './modules/converter/Converter'
import { DefaultViewerParams } from './IViewer'
export { Viewer, Converter }
export { Viewer, Converter, DefaultViewerParams }
+65
View File
@@ -0,0 +1,65 @@
import { Texture, PMREMGenerator, WebGLRenderer, TextureLoader } from 'three'
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js'
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
import { Asset, AssetType } from '../IViewer'
export class Assets {
private _cache: { [name: string]: Texture } = {}
private pmremGenerator: PMREMGenerator
public constructor(renderer: WebGLRenderer) {
this.pmremGenerator = new PMREMGenerator(renderer)
this.pmremGenerator.compileEquirectangularShader()
}
private getLoader(src: string, assetType: AssetType): TextureLoader {
if (assetType === undefined) assetType = src.split('.').pop() as AssetType
if (!Object.values(AssetType).includes(assetType)) {
console.warn(`Asset ${src} could not be loaded. Unknown type`)
return null
}
switch (assetType) {
case AssetType.TEXTURE_EXR:
return new EXRLoader()
case AssetType.TEXTURE_HDR:
return new RGBELoader()
case AssetType.TEXTURE_8BPP:
return new TextureLoader()
}
}
public getEnvironment(asset: Asset | string): Promise<Texture> {
let srcUrl: string = null
let assetType: AssetType = undefined
if ((<Asset>asset).src) {
srcUrl = (asset as Asset).src
assetType = (asset as Asset).type
} else {
srcUrl = asset as string
}
if (this._cache[srcUrl]) {
return Promise.resolve(this._cache[srcUrl])
}
return new Promise<Texture>((resolve, reject) => {
const loader = this.getLoader(srcUrl, assetType)
if (loader) {
loader.load(
srcUrl,
(texture) => {
const pmremRT = this.pmremGenerator.fromEquirectangular(texture)
this._cache[srcUrl] = pmremRT.texture
texture.dispose()
resolve(this._cache[srcUrl])
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${srcUrl} failed ${error.message}`)
}
)
} else {
reject(`Loading asset ${srcUrl} failed`)
}
})
}
}
+21 -13
View File
@@ -1,24 +1,32 @@
import * as THREE from 'three'
import Rainbow from 'rainbowvis.js'
import SpeckleStandardMaterial from './materials/SpeckleStandardMaterial'
import { Geometry } from './converter/Geometry'
export default class FilteringManager {
constructor(viewer) {
this.viewer = viewer
this.WireframeMaterial = new THREE.MeshStandardMaterial({
color: 0x7080a0,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.2,
wireframe: false
})
this.WireframeMaterial = new SpeckleStandardMaterial(
{
color: 0x7080a0,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.2,
wireframe: false
},
Geometry.USE_RTE ? ['USE_RTE'] : undefined
)
// console.log(this.viewer.sectionBox.planes)
this.ColoredMaterial = new THREE.MeshStandardMaterial({
color: 0x7080a0,
side: THREE.DoubleSide,
transparent: false,
clippingPlanes: this.viewer.sectionBox.planes
})
this.ColoredMaterial = new SpeckleStandardMaterial(
{
color: 0x7080a0,
side: THREE.DoubleSide,
transparent: false,
clippingPlanes: this.viewer.sectionBox.planes
},
Geometry.USE_RTE ? ['USE_RTE'] : undefined
)
this.colorLegend = {}
}
@@ -1,5 +1,9 @@
import * as THREE from 'three'
import SelectionHelper from './SelectionHelper'
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
import SpeckleLambertMaterial from './materials/SpeckleLambertMaterial'
import { Geometry } from './converter/Geometry'
import SpeckleLineMaterial from './materials/SpeckleLineMaterial'
export default class InteractionHandler {
constructor(viewer) {
@@ -10,23 +14,39 @@ export default class InteractionHandler {
sectionBox: this.sectionBox,
hover: false
})
this.selectionMeshMaterial = new THREE.MeshLambertMaterial({
color: 0x0b55d2,
side: THREE.DoubleSide,
wireframe: false,
transparent: true,
opacity: 0.3
})
this.selectionMeshMaterial = new SpeckleLambertMaterial(
{
color: 0x0b55d2,
side: THREE.DoubleSide,
wireframe: false,
transparent: true,
opacity: 0.3
},
Geometry.USE_RTE ? ['USE_RTE'] : []
)
this.selectionMeshMaterial.clippingPlanes = this.viewer.sectionBox.planes
// Fix overlapping faces flickering
this.selectionMeshMaterial.polygonOffset = true
this.selectionMeshMaterial.polygonOffsetFactor = -0.1
this.selectionLineMaterial = new THREE.LineBasicMaterial({ color: 0x0b55d2 })
this.selectionLineMaterial.clippingPlanes = this.viewer.sectionBox.planes
// this.selectionLineMaterial = new THREE.LineBasicMaterial({ color: 0x0b55d2 })
// this.selectionLineMaterial.clippingPlanes = this.viewer.sectionBox.planes
this.selectionEdgesMaterial = new THREE.LineBasicMaterial({ color: 0x23f3bd })
this.selectionEdgesMaterial.clippingPlanes = this.viewer.sectionBox.planes
// this.selectionEdgesMaterial = new THREE.LineBasicMaterial({ color: 0x23f3bd })
// this.selectionEdgesMaterial.clippingPlanes = this.viewer.sectionBox.planes
this.selectionLine2Material = new SpeckleLineMaterial({
color: new THREE.Color(0x0b55d2),
linewidth: 1,
worldUnits: false,
vertexColors: false,
alphaToCoverage: true,
resolution: this.viewer.renderer.getDrawingBufferSize(new THREE.Vector2()),
clippingPlanes: this.viewer.sectionBox.planes
})
// Not a fan of this, but it should be fine for now
this.selectionLine2Material.polygonOffset = true
this.selectionLine2Material.polygonOffsetFactor = -0.1
this.selectedObjects = new THREE.Group()
this.viewer.scene.add(this.selectedObjects)
@@ -34,13 +54,16 @@ export default class InteractionHandler {
this.selectionBox = new THREE.Group()
this.viewer.scene.add(this.selectionBox)
this.overlayMeshMaterial = new THREE.MeshLambertMaterial({
color: 0x57f7ff,
side: THREE.DoubleSide,
wireframe: false,
transparent: true,
opacity: 0.7
})
this.overlayMeshMaterial = new SpeckleLambertMaterial(
{
color: 0x57f7ff,
side: THREE.DoubleSide,
wireframe: false,
transparent: true,
opacity: 0.7
},
Geometry.USE_RTE ? ['USE_RTE'] : []
)
this.overlayMeshMaterial.clippingPlanes = this.viewer.sectionBox.planes
this.overlaidObjects = new THREE.Group()
this.viewer.scene.add(this.overlaidObjects)
@@ -146,10 +169,25 @@ export default class InteractionHandler {
switch (selType) {
case 'Block': {
/**
* Currently lines inside a group will not highlight
*/
const blockObjs = this.getBlockObjectsCloned(rootBlock)
for (const child of blockObjs) {
child.userData = { id: rootBlock.userData.id }
child.material = this.selectionMeshMaterial
if (child.type === 'Line2') {
const material = this.selectionLine2Material.clone()
material.color = new THREE.Color(0x0b55d2) // I really don't know why I need to reassign this...
material.linewidth = child.material.linewidth
material.worldUnits = child.material.worldUnits
material.alphaToCoverage = child.material.alphaToCoverage
material.resolution = this.viewer.renderer.getDrawingBufferSize(
new THREE.Vector2()
)
child.material = material
} else {
child.material = this.selectionMeshMaterial
}
this.selectedObjects.add(child)
//this.viewer.outlinePass.selectedObjects.push( child )
}
@@ -169,6 +207,23 @@ export default class InteractionHandler {
//this.viewer.outlinePass.selectedObjects.push( new THREE.Line( objs[0].object.geometry, this.selectionMeshMaterial ) )
break
}
case 'Line2': {
const material = this.selectionLine2Material.clone()
material.color = new THREE.Color(0x0b55d2) // I really don't know why I need to reassign this...
material.linewidth = objs[0].object.material.linewidth
material.worldUnits = objs[0].object.material.worldUnits
material.alphaToCoverage = objs[0].object.material.alphaToCoverage
material.resolution = this.viewer.renderer.getDrawingBufferSize(
new THREE.Vector2()
)
const l = new Line2(objs[0].object.geometry, material)
// Geometry.updateRTEGeometry(l.geometry)
// l.computeLineDistances()
// l.scale.set(1, 1, 1)
l.userData = { id: objs[0].object.userData.id }
this.selectedObjects.add(l)
break
}
case 'Point':
console.warn('Point selection not implemented.')
return // exit the whole func here, points cause all sorts of trouble when being selected (ie, bbox stuff)
@@ -183,6 +238,12 @@ export default class InteractionHandler {
}
const box = new THREE.Box3().setFromObject(this.selectedObjects)
if (selType === 'Line2') {
const expand = objs[0].object.material.worldUnits
? objs[0].object.material.linewidth * 0.5
: 0
box.expandByScalar(expand)
}
const boxHelper = new THREE.Box3Helper(box, 0x047efb)
this.selectionBox.clear()
this.selectionBox.add(boxHelper)
@@ -195,6 +256,7 @@ export default class InteractionHandler {
location: objs[0].point,
selectionCenter
}
// console.log(selectionInfo)
this.viewer.emit('select', selectionInfo)
}
@@ -214,6 +276,7 @@ export default class InteractionHandler {
}
for (const child of objects) {
child.geometry = child.geometry.clone().applyMatrix4(block.matrix)
Geometry.updateRTEGeometry(child.geometry)
}
return objects
}
+145 -77
View File
@@ -1,7 +1,13 @@
import * as THREE from 'three'
import debounce from 'lodash.debounce'
import SceneObjects from './SceneObjects'
import { Line2 } from 'three/examples/jsm/lines/Line2.js'
import { Vector2 } from 'three'
import { Geometry } from './converter/Geometry'
import SpeckleStandardMaterial from './materials/SpeckleStandardMaterial'
import SpeckleLineMaterial from './materials/SpeckleLineMaterial'
import SpeckleLineBasicMaterial from './materials/SpeckleLineBasicMaterial'
import SpeckleBasicMaterial from './materials/SpeckleBasicMaterial'
/**
* Manages objects and provides some convenience methods to focus on the entire scene, or one specific object.
*/
@@ -13,54 +19,7 @@ export default class SceneObjectManager {
this.sceneObjects = new SceneObjects(viewer)
this.solidMaterial = new THREE.MeshStandardMaterial({
color: 0x8d9194,
emissive: 0x0,
roughness: 1,
metalness: 0,
side: THREE.DoubleSide,
envMap: this.viewer.cubeCamera.renderTarget.texture,
clippingPlanes: this.viewer.sectionBox.planes
})
this.transparentMaterial = new THREE.MeshStandardMaterial({
color: 0xa0a4a8,
emissive: 0x0,
roughness: 0,
metalness: 0.5,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.4,
envMap: this.viewer.cubeCamera.renderTarget.texture,
clippingPlanes: this.viewer.sectionBox.planes
})
this.solidVertexMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff,
vertexColors: THREE.VertexColors,
side: THREE.DoubleSide,
reflectivity: 0,
clippingPlanes: this.viewer.sectionBox.planes
})
this.lineMaterial = new THREE.LineBasicMaterial({
color: 0x7f7f7f,
clippingPlanes: this.viewer.sectionBox.planes
})
this.pointMaterial = new THREE.PointsMaterial({
size: 2,
sizeAttenuation: false,
color: 0x7f7f7f,
clippingPlanes: this.viewer.sectionBox.planes
})
this.pointVertexColorsMaterial = new THREE.PointsMaterial({
size: 2,
sizeAttenuation: false,
vertexColors: true,
clippingPlanes: this.viewer.sectionBox.planes
})
this.initMaterials()
this.postLoad = debounce(
() => {
@@ -102,6 +61,69 @@ export default class SceneObjectManager {
]
}
initMaterials() {
if (this.solidMaterial) this.solidMaterial.dispose()
if (this.transparentMaterial) this.transparentMaterial.dispose()
if (this.solidVertexMaterial) this.solidVertexMaterial.dispose()
if (this.lineMaterial) this.lineMaterial.dispose()
if (this.pointMaterial) this.pointMaterial.dispose()
if (this.pointVertexColorsMaterial) this.pointVertexColorsMaterial.dispose()
this.solidMaterial = new SpeckleStandardMaterial(
{
color: 0x8d9194,
emissive: 0x0,
roughness: 1,
metalness: 0,
side: THREE.DoubleSide,
// envMap: this.viewer.cubeCamera.renderTarget.texture,
clippingPlanes: this.viewer.sectionBox.planes
},
Geometry.USE_RTE ? ['USE_RTE'] : undefined
)
this.transparentMaterial = new SpeckleStandardMaterial(
{
color: 0xa0a4a8,
emissive: 0x0,
roughness: 0,
metalness: 0.5,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.4,
// envMap: this.viewer.cubeCamera.renderTarget.texture,
clippingPlanes: this.viewer.sectionBox.planes
},
Geometry.USE_RTE ? ['USE_RTE'] : undefined
)
this.solidVertexMaterial = new SpeckleBasicMaterial(
{
color: 0xffffff,
vertexColors: THREE.VertexColors,
side: THREE.DoubleSide,
reflectivity: 0,
clippingPlanes: this.viewer.sectionBox.planes
},
Geometry.USE_RTE ? ['USE_RTE'] : undefined
)
this.lineMaterial = this.makeLineMaterial()
this.pointMaterial = new THREE.PointsMaterial({
size: 2,
sizeAttenuation: false,
color: 0x7f7f7f,
clippingPlanes: this.viewer.sectionBox.planes
})
this.pointVertexColorsMaterial = new THREE.PointsMaterial({
size: 2,
sizeAttenuation: false,
vertexColors: true,
clippingPlanes: this.viewer.sectionBox.planes
})
}
// Note: we might switch later down the line from cloning materials to solely
// using a few "default" ones and controlling color through vertex colors.
// For now a small compromise to speed up dev; it is not the most memory
@@ -142,7 +164,8 @@ export default class SceneObjectManager {
if (wrapper.meta.renderMaterial) {
const renderMat = wrapper.meta.renderMaterial
const color = new THREE.Color(this._argbToRGB(renderMat.diffuse))
this._normaliseColor(color)
color.convertSRGBToLinear()
// this._normaliseColor(color);
// Is it a transparent material?
if (renderMat.opacity !== 1) {
const material = this.transparentMaterial.clone()
@@ -210,11 +233,14 @@ export default class SceneObjectManager {
let material = this.lineMaterial
if (wrapper.meta.displayStyle) {
material = this.lineMaterial.clone()
// This will only add confusion since it *might* work on some platforms.
// However, it's in pixels and the displayStyle will express the thickness in world units
// This will be replaced by the upcoming change to line rendering which supports variable
// thickness in both world space and pixels
// material.linewidth = wrapper.meta.displayStyle.lineweight > 0 ? wrapper.meta.displayStyle : 1
if (wrapper.meta.displayStyle.lineweight > 0) {
material.linewidth = wrapper.meta.displayStyle.lineweight
material.worldUnits = true
material.pixelThreshold = 0.5
} else {
material.linewidth = 1
material.worldUnits = false
}
material.color = new THREE.Color(this._argbToRGB(wrapper.meta.displayStyle.color))
// material.color.convertSRGBToLinear();
@@ -227,8 +253,9 @@ export default class SceneObjectManager {
// material.color.convertSRGBToLinear();
material.clippingPlanes = this.viewer.sectionBox.planes
}
material.resolution = this.viewer.renderer.getDrawingBufferSize(new Vector2())
const line = new THREE.Line(wrapper.bufferGeometry, material)
const line = this.makeLineMesh(wrapper.bufferGeometry, material)
line.userData = wrapper.meta
line.uuid = wrapper.meta.id
if (addToScene) {
@@ -256,8 +283,8 @@ export default class SceneObjectManager {
} else if (wrapper.meta.renderMaterial) {
const renderMat = wrapper.meta.renderMaterial
const color = new THREE.Color(this._argbToRGB(renderMat.diffuse))
this._normaliseColor(color)
color.convertSRGBToLinear()
// this._normaliseColor(color);
const material = this.pointMaterial.clone()
material.clippingPlanes = this.viewer.sectionBox.planes
// material.clippingPlanes = this.viewer.interactions.sectionBox.planes
@@ -341,33 +368,74 @@ export default class SceneObjectManager {
return box
}
makeLineMesh(geometry, material) {
let line
if (Geometry.THICK_LINES) {
line = new Line2(geometry, material)
line.computeLineDistances()
line.scale.set(1, 1, 1)
} else {
line = new THREE.Line(geometry, material)
}
return line
}
makeLineMaterial() {
let lineMaterial
if (Geometry.THICK_LINES) {
lineMaterial = new SpeckleLineMaterial({
color: 0x7f7f7f,
linewidth: 1, // in world units with size attenuation, pixels otherwise
worldUnits: false,
vertexColors: false,
alphaToCoverage: false,
resolution: this.viewer.renderer.getDrawingBufferSize(new Vector2()),
clippingPlanes: this.viewer.sectionBox.planes
})
} else {
lineMaterial = new SpeckleLineBasicMaterial({
color: 0x7f7f7f,
clippingPlanes: this.viewer.sectionBox.planes
})
}
return lineMaterial
}
_argbToRGB(argb) {
return '#' + ('000000' + (argb & 0xffffff).toString(16)).slice(-6)
}
_normaliseColor(color) {
// Note: full of **magic numbers** that will need changing once global scene
// is properly set up; also to test with materials coming from other software too...
const hsl = {}
color.getHSL(hsl)
/**
* This has been retired. We're using proper srbg->linear conversions now
* @param {*} color
* @returns
*/
// _normaliseColor(color) {
// return color
// // Note: full of **magic numbers** that will need changing once global scene
// // is properly set up; also to test with materials coming from other software too...
// const hsl = {}
// color.getHSL(hsl)
if (hsl.s + hsl.l > 1) {
while (hsl.s + hsl.l > 1) {
hsl.s -= 0.05
hsl.l -= 0.05
}
}
// if (hsl.s + hsl.l > 1) {
// while (hsl.s + hsl.l > 1) {
// hsl.s -= 0.05
// hsl.l -= 0.05
// }
// }
if (hsl.l > 0.6) {
hsl.l = 0.6
}
// if (hsl.l > 0.6) {
// hsl.l = 0.6
// }
if (hsl.l < 0.3) {
hsl.l = 0.3
}
// if (hsl.l < 0.3) {
// hsl.l = 0.3
// }
color.setHSL(hsl.h, hsl.s, hsl.l)
}
// color.setHSL(hsl.h, hsl.s, hsl.l)
// }
_srgbToLinear(x) {
if (x <= 0) return 0
+5 -1
View File
@@ -1,5 +1,6 @@
import * as THREE from 'three'
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils'
import { Geometry } from './converter/Geometry'
import FilteringManager from './FilteringManager'
/**
@@ -44,6 +45,7 @@ export default class SceneObjects {
// When the `appliedFilter` is null, scene will contain `allObjects`. Otherwise, `filteredObjects`
// This is to optimize the no-filter usecase, so we don't make an unnecessary clone of all the objects
this.objectsInScene = this.allObjects
this.scene.add(this.allObjects)
this.isBusy = true
@@ -257,6 +259,7 @@ export default class SceneObjects {
for (const element of acc) {
element.geometry = element.geometry.clone()
element.geometry.applyMatrix4(group.matrix)
Geometry.updateRTEGeometry(element.geometry) // TEMPORARY!!!
}
return acc
}
@@ -289,7 +292,8 @@ export default class SceneObjects {
) {
// if ( mesh.type === 'Line' ) continue
// if ( groupedObjects.children.length >= 2 ) continue
groupedObjects.add(mesh.clone())
const clone = mesh.clone()
groupedObjects.add(clone)
continue
}
@@ -17,6 +17,8 @@ export default class SelectionHelper extends EventEmitter {
this.viewer = parent
this.raycaster = new THREE.Raycaster()
this.raycaster.params.Line.threshold = 0.1
this.raycaster.params.Line2 = {}
this.raycaster.params.Line2.threshold = 1
// optional param allows for raycasting against a subset of objects
// this.subset = typeof _options !== 'undefined' && typeof _options.subset !== 'undefined' ? _options.subset : null;
@@ -112,10 +114,14 @@ export default class SelectionHelper extends EventEmitter {
normalizedPosition,
this.viewer.cameraHandler.activeCam.camera
)
/**
* This 'subset' thing is really weird and it's breaking picking. I would gladly
* do something about it, however I'm afraid that it will open up a can of worms,
* which are out-of-scope for now 26.05.2022
*/
const targetObjects = this.subset
? this.subset
: this.viewer.sceneManager.filteredObjects
let intersectedObjects = this.raycaster.intersectObjects(targetObjects)
// filters objects in section box mode
if (this.viewer.sectionBox.display.visible && this.checkForSectionBoxInclusion) {
+336
View File
@@ -0,0 +1,336 @@
import * as THREE from 'three'
import Stats from 'three/examples/jsm/libs/stats.module'
import ObjectManager from './SceneObjectManager'
import ViewerObjectLoader from './ViewerObjectLoader'
import EventEmitter from './EventEmitter'
import InteractionHandler from './InteractionHandler'
import CameraHandler from './context/CameraHanlder'
import SectionBox from './SectionBox'
import { Clock, CubeCamera, Texture, Vector3 } from 'three'
import { Scene } from 'three'
import { WebGLRenderer } from 'three'
import { Assets } from './Assets'
import { Optional } from '../helpers/typeHelper'
import { DefaultViewerParams, IViewer, ViewerParams } from '../IViewer'
import { World } from './World'
import { Geometry } from './converter/Geometry'
export class Viewer extends EventEmitter implements IViewer {
private clock: Clock
private container: HTMLElement
private cubeCamera: CubeCamera
private stats: Optional<Stats>
private loaders: { [id: string]: ViewerObjectLoader } = {}
private needsRender: boolean
private inProgressOperations: number
public scene: Scene
public sectionBox: SectionBox
public sceneManager: ObjectManager
public interactions: InteractionHandler
private renderer: WebGLRenderer
public cameraHandler: CameraHandler
private sceneURL = '' // Temporary
private startupParams: ViewerParams
public static Assets: Assets
private _worldOrigin: Vector3 = new Vector3()
public get worldSize() {
World.worldBox.getCenter(this._worldOrigin)
const size = new Vector3().subVectors(World.worldBox.max, World.worldBox.min)
return {
x: size.x,
y: size.y,
z: size.z
}
}
public get worldOrigin() {
return this._worldOrigin
}
public get RTE(): boolean {
return Geometry.USE_RTE
}
public set RTE(value: boolean) {
;(async () => {
await this.unloadAll()
Geometry.USE_RTE = value
this.sceneManager.initMaterials()
World.resetWorld()
await this.loadObject(this.sceneURL, undefined, undefined)
})()
}
public get thickLines(): boolean {
return Geometry.THICK_LINES
}
public set thickLines(value: boolean) {
;(async () => {
await this.unloadAll()
Geometry.THICK_LINES = value
this.sceneManager.initMaterials()
World.resetWorld()
await this.loadObject(this.sceneURL, undefined, undefined)
})()
}
public constructor(
container: HTMLElement,
params: ViewerParams = DefaultViewerParams
) {
super()
window.THREE = THREE
this.startupParams = params
this.clock = new THREE.Clock()
this.container = container || document.getElementById('renderer')
this.scene = new THREE.Scene()
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
preserveDrawingBuffer: true
})
this.renderer.setClearColor(0xcccccc, 0)
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.outputEncoding = THREE.sRGBEncoding
this.renderer.toneMapping = THREE.LinearToneMapping
this.renderer.toneMappingExposure = 0.5
this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight)
this.container.appendChild(this.renderer.domElement)
Viewer.Assets = new Assets(this.renderer)
this.cameraHandler = new CameraHandler(this)
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(512, {
format: THREE.RGBFormat,
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter
})
this.cubeCamera = new THREE.CubeCamera(0.1, 10_000, cubeRenderTarget)
this.scene.add(this.cubeCamera)
if (params.showStats) {
this.stats = Stats()
this.container.appendChild(this.stats.dom)
}
window.addEventListener('resize', this.onWindowResize.bind(this), false)
this.loaders = {}
this.sectionBox = new SectionBox(this)
this.sectionBox.off()
this.sceneManager = new ObjectManager(this)
this.interactions = new InteractionHandler(this)
this.sceneLights()
this.animate()
this.onWindowResize()
this.interactions.zoomExtents()
this.needsRender = true
this.inProgressOperations = 0
}
public async init(): Promise<void> {
if (this.startupParams.environmentSrc) {
Viewer.Assets.getEnvironment(this.startupParams.environmentSrc)
.then((value: Texture) => {
this.scene.environment = value
})
.catch((reason) => {
console.warn(reason)
console.warn('Fallback to null environment!')
})
}
}
private sceneLights() {
// const dirLight = new THREE.DirectionalLight( 0xffffff, 0.1 )
// dirLight.color.setHSL( 0.1, 1, 0.95 )
// dirLight.position.set( -1, 1.75, 1 )
// dirLight.position.multiplyScalar( 1000 )
// this.scene.add( dirLight )
// const dirLight2 = new THREE.DirectionalLight( 0xffffff, 0.9 )
// dirLight2.color.setHSL( 0.1, 1, 0.95 )
// dirLight2.position.set( 0, -1.75, 1 )
// dirLight2.position.multiplyScalar( 1000 )
// this.scene.add( dirLight2 )
// const hemiLight2 = new THREE.HemisphereLight( 0xffffff, new THREE.Color( '#232323' ), 1.9 )
// hemiLight2.color.setHSL( 1, 1, 1 )
// // hemiLight2.groundColor = new THREE.Color( '#232323' )
// hemiLight2.up.set( 0, 0, 1 )
// this.scene.add( hemiLight2 )
// let axesHelper = new THREE.AxesHelper( 1 )
// this.scene.add( axesHelper )
// return
const ambientLight = new THREE.AmbientLight(0xffffff)
this.scene.add(ambientLight)
const lights = []
lights[0] = new THREE.PointLight(0xffffff, 0.21, 0)
lights[1] = new THREE.PointLight(0xffffff, 0.21, 0)
lights[2] = new THREE.PointLight(0xffffff, 0.21, 0)
lights[3] = new THREE.PointLight(0xffffff, 0.21, 0)
const factor = 1000
lights[0].position.set(1 * factor, 1 * factor, 1 * factor)
lights[1].position.set(1 * factor, -1 * factor, 1 * factor)
lights[2].position.set(-1 * factor, -1 * factor, 1 * factor)
lights[3].position.set(-1 * factor, 1 * factor, 1 * factor)
this.scene.add(lights[0])
this.scene.add(lights[1])
this.scene.add(lights[2])
this.scene.add(lights[3])
// let sphereSize = 0.2
// this.scene.add( new THREE.PointLightHelper( lights[ 0 ], sphereSize ) )
// this.scene.add( new THREE.PointLightHelper( lights[ 1 ], sphereSize ) )
// this.scene.add( new THREE.PointLightHelper( lights[ 2 ], sphereSize ) )
// this.scene.add( new THREE.PointLightHelper( lights[ 3 ], sphereSize ) )
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x0, 0.2)
hemiLight.color.setHSL(1, 1, 1)
hemiLight.groundColor.setHSL(0.095, 1, 0.75)
hemiLight.up.set(0, 0, 1)
this.scene.add(hemiLight)
const group = new THREE.Group()
this.scene.add(group)
}
onWindowResize() {
this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight)
this.needsRender = true
}
private animate() {
const delta = this.clock.getDelta()
const hasControlsUpdated = this.cameraHandler.controls.update(delta)
requestAnimationFrame(this.animate.bind(this))
// you can skip this condition to render though
if (hasControlsUpdated || this.needsRender) {
this.needsRender = false
if (this.stats) this.stats.begin()
this.render()
const infoDrawsEl = document.getElementById('info-draws')
if (this.stats && infoDrawsEl) {
infoDrawsEl.textContent = '' + this.renderer.info.render.calls
}
if (this.stats) this.stats.end()
}
}
private render() {
this.renderer.render(this.scene, this.cameraHandler.activeCam.camera)
}
public toggleSectionBox() {
this.sectionBox.toggle()
}
public sectionBoxOff() {
this.sectionBox.off()
}
public sectionBoxOn() {
this.sectionBox.on()
}
public zoomExtents(fit?: number, transition?: boolean) {
this.interactions.zoomExtents(fit, transition)
}
public setProjectionMode(mode: typeof CameraHandler.prototype.activeCam) {
this.cameraHandler.activeCam = mode
}
public toggleCameraProjection() {
this.cameraHandler.toggleCameras()
}
public async loadObject(
url: string,
token: string | undefined,
enableCaching = true
) {
try {
if (++this.inProgressOperations === 1) (this as EventEmitter).emit('busy', true)
const loader = new ViewerObjectLoader(this, url, token, enableCaching)
this.loaders[url] = loader
await loader.load()
} finally {
if (--this.inProgressOperations === 0) (this as EventEmitter).emit('busy', false)
this.sceneURL = url
}
}
public async cancelLoad(url: string, unload = false) {
this.loaders[url].cancelLoad()
if (unload) {
await this.unloadObject(url)
}
return
}
public async unloadObject(url: string) {
try {
if (++this.inProgressOperations === 1) (this as EventEmitter).emit('busy', true)
await this.loaders[url].unload()
delete this.loaders[url]
} finally {
if (--this.inProgressOperations === 0) (this as EventEmitter).emit('busy', false)
}
}
public async unloadAll() {
for (const key of Object.keys(this.loaders)) {
await this.loaders[key].unload()
delete this.loaders[key]
}
await this.applyFilter(null)
return
}
public async applyFilter(filter: unknown) {
try {
if (++this.inProgressOperations === 1) (this as EventEmitter).emit('busy', true)
this.interactions.deselectObjects()
return await this.sceneManager.sceneObjects.applyFilter(filter)
} finally {
if (--this.inProgressOperations === 0) (this as EventEmitter).emit('busy', false)
}
}
public getObjectsProperties(includeAll = true) {
return this.sceneManager.sceneObjects.getObjectsProperties(includeAll)
}
public dispose() {
// TODO: currently it's easier to simply refresh the page :)
}
}
@@ -67,6 +67,7 @@ export default class ViewerObjectLoader {
let total = 0
let viewerLoads = 0
let firstObjectPromise = null
const parsedObjects = [] // Temporary until refactor
for await (const obj of this.loader.getObjectIterator()) {
if (this.cancel) {
this.viewer.emit('load-progress', {
@@ -79,12 +80,13 @@ export default class ViewerObjectLoader {
}
await this.converter.asyncPause()
if (first) {
// console.log(obj)
firstObjectPromise = this.converter.traverseAndConvert(
obj,
async (objectWrapper) => {
await this.converter.asyncPause()
objectWrapper.meta.__importedUrl = this.objectUrl
this.viewer.sceneManager.addObject(objectWrapper)
parsedObjects.push(objectWrapper) // Temporary until refactor
viewerLoads++
}
)
@@ -102,7 +104,14 @@ export default class ViewerObjectLoader {
await firstObjectPromise
}
// Geometry.applyWorldTransform(parsedObjects)
// Temporary until refactor
for (let k = 0; k < parsedObjects.length; k++) {
await this.converter.asyncPause()
this.viewer.sceneManager.addObject(parsedObjects[k])
}
await this.viewer.sceneManager.postLoadFunction()
this.viewer.emit('load-complete')
if (viewerLoads === 0) {
console.warn(`Viewer: no 3d objects found in object ${this.objectId}`)
@@ -10,7 +10,7 @@ import CameraHandler from './context/CameraHanlder'
import SectionBox from './SectionBox'
export default class Viewer extends EventEmitter {
export default class Viewer_old extends EventEmitter {
constructor({
container,
postprocessing = false,
+29
View File
@@ -0,0 +1,29 @@
import { Box3 } from 'three'
export class World {
/* This will no longer exist when we have a scene tree */
private static readonly boxes: Array<Box3> = new Array<Box3>()
public static readonly worldBox: Box3 = new Box3()
public static expandWorld(box: Box3) {
World.boxes.push(box)
World.updateWorld()
}
public static reduceWorld(box: Box3) {
World.boxes.splice(World.boxes.indexOf(box), 1)
World.updateWorld()
}
public static updateWorld() {
World.worldBox.makeEmpty()
for (let k = 0; k < this.boxes.length; k++) {
World.worldBox.union(World.boxes[k])
}
}
public static resetWorld() {
World.worldBox.makeEmpty()
this.boxes.length = 0
}
}
@@ -1,20 +1,73 @@
import * as THREE from 'three'
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils'
// import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils'
import ObjectWrapper from './ObjectWrapper'
import { getConversionFactor } from './Units'
import MeshTriangulationHelper from './MeshTriangulationHelper'
import { Matrix4 } from 'three'
import { Geometry, GeometryData } from './Geometry'
import { BoxBufferGeometry, EllipseCurve, Matrix4, Vector2 } from 'three'
import { Vector3 } from 'three'
import { Line3 } from 'three'
export type ConverterResultDelegate = (
object: ObjectWrapper | undefined
) => Promise<void>
export type ConverterGeometryDelegate = (
object,
scale?: boolean
) => Promise<ObjectWrapper | undefined>
export type ConverterGeometryDataDelegate = (
object,
scale?: boolean
) => Promise<GeometryData>
/**
* Utility class providing some top level conversion methods.
* Warning: HIC SVNT DRACONES.
*/
export default class Coverter {
constructor(objectLoader) {
private objectLoader
private curveSegmentLength: number
private lastAsyncPause: number
private activePromises: number
private maxChildrenPromises: number
private readonly GeometryConverterMapping: {
[name: string]: ConverterGeometryDelegate
} = {
View3D: this.View3DToBufferGeometry.bind(this),
BlockInstance: this.BlockInstanceToBufferGeometry.bind(this),
Pointcloud: this.PointcloudToBufferGeometry.bind(this),
Brep: this.BrepToBufferGeometry.bind(this),
Mesh: this.MeshToBufferGeometry.bind(this),
Point: this.PointToBufferGeometry.bind(this),
Line: this.LineToBufferGeometry.bind(this),
Polyline: this.PolylineToBufferGeometry.bind(this),
Box: this.BoxToBufferGeometry.bind(this),
Polycurve: this.PolycurveToBufferGeometry.bind(this),
Curve: this.CurveToBufferGeometry.bind(this),
Circle: this.CircleToBufferGeometry.bind(this),
Arc: this.ArcToBufferGeometry.bind(this),
Ellipse: this.EllipseToBufferGeometry.bind(this)
}
private readonly GeometryDataConverterMapping: {
[name: string]: ConverterGeometryDataDelegate
} = {
View3D: this.View3DToBufferGeometry.bind(this),
BlockInstance: this.BlockInstanceToBufferGeometry.bind(this),
Pointcloud: this.PointcloudToGeometryData.bind(this),
Brep: this.MeshToGeometryData.bind(this),
Mesh: this.MeshToGeometryData.bind(this),
Point: this.PointToGeometryData.bind(this),
Line: this.LineToGeometryData.bind(this),
Polyline: this.PolylineToGeometryData.bind(this),
Box: this.BoxToGeometryData.bind(this),
Polycurve: this.PolycurveToGeometryData.bind(this),
Curve: this.PolylineToGeometryData.bind(this),
Circle: this.CircleToGeometryData.bind(this),
Arc: this.ArcToGeometryData.bind(this),
Ellipse: this.EllipseToGeometryData.bind(this)
}
constructor(objectLoader: unknown) {
if (!objectLoader) {
console.warn(
'Converter initialized without a corresponding object loader. Any objects that include references will throw errors.'
@@ -29,7 +82,7 @@ export default class Coverter {
this.maxChildrenPromises = 200
}
async asyncPause() {
private async asyncPause() {
// Don't freeze the UI when doing all those traversals
if (Date.now() - this.lastAsyncPause >= 100) {
this.lastAsyncPause = Date.now()
@@ -44,7 +97,12 @@ export default class Coverter {
* @param {Function} callback [description]
* @return {[type]} [description]
*/
async traverseAndConvert(obj, callback, scale = true, parents = []) {
public async traverseAndConvert(
obj,
callback: ConverterResultDelegate,
scale = true,
parents: [] = []
) {
await this.asyncPause()
// Exit on primitives (string, ints, bools, bigints, etc.)
@@ -81,9 +139,9 @@ export default class Coverter {
// If we can convert it, we should invoke the respective conversion routine.
const type = this.getSpeckleType(obj)
if (this[`${type}ToBufferGeometry`]) {
if (this.directConverterExists(obj)) {
try {
await callback(await this[`${type}ToBufferGeometry`](obj.data || obj, scale))
await callback(await this.directConvert(obj.data || obj, scale))
return
} catch (e) {
console.warn(
@@ -109,9 +167,9 @@ export default class Coverter {
const convertedElement = await this.convert(displayValue, scale)
await callback(
new ObjectWrapper(
convertedElement.bufferGeometry,
convertedElement?.bufferGeometry,
obj,
convertedElement.geometryType
convertedElement?.geometryType
)
) // use the parent's metadata!
} catch (e) {
@@ -126,9 +184,9 @@ export default class Coverter {
const convertedElement = await this.convert(val, scale)
await callback(
new ObjectWrapper(
convertedElement.bufferGeometry,
convertedElement?.bufferGeometry,
{ renderMaterial: val.renderMaterial, ...obj },
convertedElement.geometryType
convertedElement?.geometryType
)
)
}
@@ -176,11 +234,19 @@ export default class Coverter {
this.activePromises -= childrenConversionPromisses.length
}
directConverterExists(obj) {
return this[`${this.getSpeckleType(obj)}ToBufferGeometry`] !== undefined
private directConverterExists(obj) {
return this.getSpeckleType(obj) in this.GeometryConverterMapping
}
getDisplayValue(obj) {
private directConvert(obj, scale = true): Promise<ObjectWrapper | undefined> {
return this.GeometryConverterMapping[this.getSpeckleType(obj)](obj, scale)
}
private convertToGeometryData(obj, scale = true): Promise<GeometryData> {
return this.GeometryDataConverterMapping[this.getSpeckleType(obj)](obj, scale)
}
private getDisplayValue(obj) {
return (
obj['displayValue'] ||
obj['@displayValue'] ||
@@ -196,24 +262,12 @@ export default class Coverter {
* @param {Function} callback [description]
* @return {[type]} [description]
*/
async convert(obj, scale = true) {
private async convert(obj, scale = true) {
if (obj.referencedId) obj = await this.resolveReference(obj)
try {
const type = this.getSpeckleType(obj)
if (this[`${type}ToBufferGeometry`]) {
return await this[`${type}ToBufferGeometry`](obj.data || obj, scale)
if (this.directConverterExists(obj)) {
return await this.directConvert(obj.data || obj, scale)
}
/**
* Regarding #723. This would be more generic and possibly handle other
* types with missing direct convertor, however I don't feel it is the
* 'convert' fuction's place to handle this...
*/
// else {
// let element;
// if((element = this.getDisplayValue(obj)) !== undefined) {
// return await this.convert(element, scale);
// }
// }
return null
} catch (e) {
console.warn(`(Direct convert) Failed to convert object with id: ${obj.id}`)
@@ -226,7 +280,7 @@ export default class Coverter {
* @param {[type]} arr [description]
* @return {[type]} [description]
*/
async dechunk(arr) {
private async dechunk(arr) {
if (!arr || arr.length === 0) return arr
// Handles pre-chunking objects, or arrs that have not been chunked
if (!arr[0].referencedId) return arr
@@ -248,7 +302,7 @@ export default class Coverter {
* @param {[type]} obj [description]
* @return {[type]} [description]
*/
async resolveReference(obj) {
private async resolveReference(obj) {
if (obj.referencedId) {
const resolvedObj = await this.objectLoader.getObject(obj.referencedId)
// this.asyncPause()
@@ -261,7 +315,7 @@ export default class Coverter {
* @param {[type]} obj [description]
* @return {[type]} [description]
*/
getSpeckleType(obj) {
private getSpeckleType(obj): string {
let type = 'Base'
if (obj.data)
type = obj.data.speckle_type
@@ -271,7 +325,10 @@ export default class Coverter {
return type
}
async View3DToBufferGeometry(obj) {
/**
* VIEW 3D
*/
private async View3DToBufferGeometry(obj) {
obj.origin.units = obj.units
obj.target.units = obj.units
const origin = this.PointToVector3(obj.origin)
@@ -281,13 +338,22 @@ export default class Coverter {
return new ObjectWrapper(obj, obj, 'View')
}
async BlockInstanceToBufferGeometry(obj, scale) {
/**
* BLOCK INSTANCE
*/
private async BlockInstanceToBufferGeometry(obj, scale?: boolean) {
const cF = scale ? getConversionFactor(obj.units) : 1
const definition = await this.resolveReference(obj.blockDefinition)
const matrix = new THREE.Matrix4().set(
...(Array.isArray(obj.transform) ? obj.transform : obj.transform.value)
)
/**
* Speckle matrices are row major. Three's 'fromArray' function assumes
* the matrix is in column major. That's why we transpose it here.
*/
const matrixData: number[] = Array.isArray(obj.transform)
? obj.transform
: obj.transform.value
const matrix = new Matrix4().fromArray(matrixData).transpose()
const geoms = []
for (const obj of definition.geometry) {
// Note: we are passing scale = false to the conversion of all objects, as scaling *needs* to happen
@@ -301,60 +367,52 @@ export default class Coverter {
return new ObjectWrapper(geoms, obj, 'block', {
transformMatrix: matrix,
scaleMatrix: new THREE.Matrix4().makeScale(cF, cF, cF)
scaleMatrix: new Matrix4().makeScale(cF, cF, cF)
})
}
async PointcloudToBufferGeometry(obj, scale = true) {
/**
* POINT CLOUD
*/
private async PointcloudToGeometryData(obj, scale = true) {
const conversionFactor = scale ? getConversionFactor(obj.units) : 1
const buffer = new THREE.BufferGeometry()
const vertices = await this.dechunk(obj.points)
buffer.setAttribute(
'position',
new THREE.Float32BufferAttribute(
!scale || conversionFactor === 1
? vertices
: vertices.map((v) => v * conversionFactor),
3
)
)
const colorsRaw = await this.dechunk(obj.colors)
let colors = null
if (colorsRaw && colorsRaw.length !== 0) {
if (colorsRaw.length !== buffer.attributes.position.count) {
if (colorsRaw.length !== vertices.length / 3) {
console.warn(
`Mesh (id ${obj.id}) colours are mismatched with vertice counts. The number of colours must equal the number of vertices.`
)
}
buffer.setAttribute(
'color',
new THREE.BufferAttribute(
new Float32Array(buffer.attributes.position.count * 3),
3
)
)
for (let i = 0; i < buffer.attributes.position.count; i++) {
const color = colorsRaw[i]
const r = (color >> 16) & 0xff
const g = (color >> 8) & 0xff
const b = color & 0xff
buffer.attributes.color.setXYZ(i, r / 255, g / 255, b / 255)
}
colors = Geometry.unpackColors(colorsRaw)
}
// delete obj.points
// delete obj.colors
// delete obj.sizes // note, these might be used in the future
return new ObjectWrapper(buffer, obj, 'pointcloud')
return {
attributes: {
POSITION: vertices,
COLOR: colors
},
bakeTransform: scale
? new Matrix4().makeScale(conversionFactor, conversionFactor, conversionFactor)
: null,
transform: null
} as GeometryData
}
async BrepToBufferGeometry(obj, scale = true) {
private async PointcloudToBufferGeometry(obj, scale = true) {
return new ObjectWrapper(
Geometry.makePointCloudGeometry(await this.PointcloudToGeometryData(obj, scale)),
obj,
'pointcloud'
)
}
/**
* BREP
*/
private async BrepToBufferGeometry(obj, scale = true) {
try {
if (!obj) return
@@ -367,8 +425,6 @@ export default class Coverter {
)
// deletes known unneeded fields
// delete obj.displayMesh
// delete obj.displayValue
delete obj.Edges
delete obj.Faces
delete obj.Loops
@@ -385,149 +441,177 @@ export default class Coverter {
}
}
async MeshToBufferGeometry(obj, scale = true) {
try {
if (!obj) return
/**
* MESH
*/
private async MeshToGeometryData(obj, scale = true): Promise<GeometryData> {
if (!obj) return
const conversionFactor = getConversionFactor(obj.units)
const buffer = new THREE.BufferGeometry()
const indices = []
const conversionFactor = getConversionFactor(obj.units)
// const buffer = new BufferGeometry()
const indices = []
if (!obj.vertices) return
if (!obj.faces) return
if (!obj.vertices) return
if (!obj.faces) return
const vertices = await this.dechunk(obj.vertices)
const faces = await this.dechunk(obj.faces)
const vertices = await this.dechunk(obj.vertices)
const faces = await this.dechunk(obj.faces)
const colorsRaw = await this.dechunk(obj.colors)
let colors = null
let k = 0
while (k < faces.length) {
let n = faces[k]
if (n <= 3) n += 3 // 0 -> 3, 1 -> 4
let k = 0
while (k < faces.length) {
let n = faces[k]
if (n <= 3) n += 3 // 0 -> 3, 1 -> 4
if (n === 3) {
// Triangle face
indices.push(faces[k + 1], faces[k + 2], faces[k + 3])
} else {
// Quad or N-gon face
const triangulation = MeshTriangulationHelper.triangulateFace(
k,
faces,
vertices
)
indices.push(...triangulation)
}
k += n + 1
}
if (vertices.length >= 65535 || indices.length >= 65535) {
buffer.setIndex(new THREE.Uint32BufferAttribute(indices, 1))
if (n === 3) {
// Triangle face
indices.push(faces[k + 1], faces[k + 2], faces[k + 3])
} else {
buffer.setIndex(new THREE.Uint16BufferAttribute(indices, 1))
// Quad or N-gon face
const triangulation = MeshTriangulationHelper.triangulateFace(
k,
faces,
vertices
)
indices.push(
...triangulation.filter((el) => {
return el !== undefined
})
)
}
buffer.setAttribute(
'position',
new THREE.Float32BufferAttribute(
!scale || conversionFactor === 1
? vertices
: vertices.map((v) => v * conversionFactor),
3
k += n + 1
}
if (colorsRaw && colorsRaw.length !== 0) {
if (colorsRaw.length !== vertices.length / 3) {
console.warn(
`Mesh (id ${obj.id}) colours are mismatched with vertice counts. The number of colours must equal the number of vertices.`
)
}
colors = Geometry.unpackColors(colorsRaw)
}
return {
attributes: {
POSITION: vertices,
INDEX: indices,
COLOR: colors
},
bakeTransform: scale
? new Matrix4().makeScale(conversionFactor, conversionFactor, conversionFactor)
: null,
transform: null
} as GeometryData
}
private async MeshToBufferGeometry(obj, scale = true) {
try {
return new ObjectWrapper(
Geometry.makeMeshGeometry(await this.MeshToGeometryData(obj, scale)),
obj
)
const colorsRaw = await this.dechunk(obj.colors)
if (colorsRaw && colorsRaw.length !== 0) {
if (colorsRaw.length !== buffer.attributes.position.count) {
console.warn(
`Mesh (id ${obj.id}) colours are mismatched with vertice counts. The number of colours must equal the number of vertices.`
)
}
buffer.setAttribute(
'color',
new THREE.BufferAttribute(
new Float32Array(buffer.attributes.position.count * 3),
3
)
)
for (let i = 0; i < buffer.attributes.position.count; i++) {
const color = colorsRaw[i]
const r = (color >> 16) & 0xff
const g = (color >> 8) & 0xff
const b = color & 0xff
buffer.attributes.color.setXYZ(i, r / 255, g / 255, b / 255)
}
}
buffer.computeVertexNormals()
//buffer.computeFaceNormals( )
buffer.computeBoundingSphere()
// delete obj.vertices
// delete obj.faces
// delete obj.colors
return new ObjectWrapper(buffer, obj)
} catch (e) {
console.warn(`Failed to convert mesh with id: ${obj.id}`)
throw e
}
}
async PointToBufferGeometry(obj, scale = true) {
const v = this.PointToVector3(obj, scale)
const buf = new THREE.BufferGeometry().setFromPoints([v])
return new ObjectWrapper(buf, obj, 'point')
/**
* POINT
*/
private async PointToGeometryData(obj, scale = true): Promise<GeometryData> {
const conversionFactor = scale ? getConversionFactor(obj.units) : 1
return {
attributes: {
POSITION: this.PointToFloatArray(obj)
},
bakeTransform: scale
? new Matrix4().makeScale(conversionFactor, conversionFactor, conversionFactor)
: null,
transform: null
} as GeometryData
}
async LineToBufferGeometry(object, scale = true) {
if (object.value) {
//Old line format, treat as polyline
return this.PolylineToBufferGeometry(object, scale)
}
const obj = {}
Object.assign(obj, object)
private async PointToBufferGeometry(obj, scale = true) {
return new ObjectWrapper(
Geometry.makePointGeometry(await this.PointToGeometryData(obj, scale)),
obj,
'point'
)
}
const geometry = new THREE.BufferGeometry().setFromPoints([
this.PointToVector3(obj.start, scale),
this.PointToVector3(obj.end, scale)
])
/**
* LINE
*/
private async LineToGeometryData(obj, scale = true): Promise<GeometryData> {
const conversionFactor = scale ? getConversionFactor(obj.units) : 1
return {
attributes: {
POSITION: this.PointToFloatArray(obj.start).concat(
this.PointToFloatArray(obj.end)
)
},
bakeTransform: scale
? new Matrix4().makeScale(conversionFactor, conversionFactor, conversionFactor)
: null,
transform: null
} as GeometryData
}
private async LineToBufferGeometry(obj, scale = true) {
if (obj.value) {
//Old line format, treat as polyline
return this.PolylineToBufferGeometry(obj, scale)
}
const geometry = Geometry.makeLineGeometry(
await this.LineToGeometryData(obj, scale)
)
return new ObjectWrapper(geometry, obj, 'line')
}
async PolylineToBufferGeometry(object, scale = true) {
const obj = {}
/**
* POLYLINE
*/
private async PolylineToGeometryData(object, scale = true): Promise<GeometryData> {
const obj = Object.create({})
Object.assign(obj, object)
const conversionFactor = scale ? getConversionFactor(obj.units) : 1
obj.value = await this.dechunk(obj.value)
const points = []
for (let i = 0; i < obj.value.length; i += 3) {
points.push(
new THREE.Vector3(
obj.value[i] * conversionFactor,
obj.value[i + 1] * conversionFactor,
obj.value[i + 2] * conversionFactor
)
)
}
if (obj.closed) points.push(points[0])
if (obj.closed) obj.value.push(obj.value[0], obj.value[1], obj.value[2])
return {
attributes: {
POSITION: obj.value
},
bakeTransform: scale
? new Matrix4().makeScale(conversionFactor, conversionFactor, conversionFactor)
: null,
transform: null
} as GeometryData
}
const geometry = new THREE.BufferGeometry().setFromPoints(points)
delete obj.value
delete obj.bbox
async PolylineToBufferGeometry(obj, scale = true) {
const geometry = Geometry.makeLineGeometry(
await this.PolylineToGeometryData(obj, scale)
)
return new ObjectWrapper(geometry, obj, 'line')
}
async BoxToBufferGeometry(object, scale = true) {
/**
* BOX
*/
private async BoxToGeometryData(object, scale = true) {
/**
* Right, so we're cheating here a bit. We're using three's box geometry
* to get the vertices and indices. Normally we could(should) do that by hand
* but it's too late in the evenning atm...
*/
const conversionFactor = scale ? getConversionFactor(object.units) : 1
const move = this.PointToVector3(object.basePlane.origin)
@@ -535,33 +619,57 @@ export default class Coverter {
const depth = (object.ySize.end - object.ySize.start) * conversionFactor
const height = (object.zSize.end - object.zSize.start) * conversionFactor
const box = new THREE.BoxBufferGeometry(width, depth, height, 1, 1, 1)
box.applyMatrix4(new THREE.Matrix4().setPosition(move))
return new ObjectWrapper(box, object)
const box = new BoxBufferGeometry(width, depth, height, 1, 1, 1)
return {
attributes: {
POSITION: box.attributes.position.array,
INDEX: box.index.array
},
bakeTransform: new Matrix4().setPosition(move),
transform: null
} as GeometryData
}
async BoxToBufferGeometry(object, scale = true) {
return new ObjectWrapper(
Geometry.makeMeshGeometry(await this.BoxToGeometryData(object, scale)),
object
)
}
async PolycurveToBufferGeometry(object, scale = true) {
const obj = {}
/**
* POLYCURVE
*/
async PolycurveToGeometryData(object, scale = true): Promise<GeometryData> {
const obj = Object.create({})
Object.assign(obj, object)
const buffers = []
for (let i = 0; i < obj.segments.length; i++) {
let element = obj.segments[i]
let conv
if (this.directConverterExists(element)) conv = await this.convert(element, scale)
if (this.directConverterExists(element))
conv = await this.convertToGeometryData(element, scale)
else if ((element = this.getDisplayValue(element)) !== undefined)
conv = await this.convert(element, scale)
conv = await this.convertToGeometryData(element, scale)
buffers.push(conv?.bufferGeometry)
buffers.push(conv)
}
const geometry = BufferGeometryUtils.mergeBufferGeometries(buffers)
return new ObjectWrapper(geometry, obj, 'line')
return Geometry.mergeGeometryData(buffers)
}
async PolycurveToBufferGeometry(object, scale = true) {
const geometryData: GeometryData = await this.PolycurveToGeometryData(object, scale)
const geometry = Geometry.makeLineGeometry(geometryData)
return new ObjectWrapper(geometry, Object.assign({}, object), 'line')
}
/**
* CURVE
*/
async CurveToBufferGeometry(object, scale = true) {
const obj = {}
const obj = Object.create({})
Object.assign(obj, object)
const displayValue = await this.resolveReference(obj.displayValue)
displayValue.units = displayValue.units || obj.units
@@ -571,44 +679,31 @@ export default class Coverter {
return new ObjectWrapper(poly.bufferGeometry, obj, 'line')
}
async CircleToBufferGeometry(obj, scale = true) {
/**
* CIRCLE
*/
async CircleToGeometryData(obj, scale = true) {
const conversionFactor = scale ? getConversionFactor(obj.units) : 1
const points = this.getCircularCurvePoints(obj.plane, obj.radius * conversionFactor)
const geometry = new THREE.BufferGeometry().setFromPoints(points)
// delete obj.plane
// delete obj.value
// delete obj.speckle_type
// delete obj.bbox
return {
attributes: {
POSITION: this.FlattenVector3Array(points)
},
bakeTransform: null,
transform: null
} as GeometryData
}
async CircleToBufferGeometry(obj, scale = true) {
const geometry = Geometry.makeLineGeometry(
await this.CircleToGeometryData(obj, scale)
)
return new ObjectWrapper(geometry, obj, 'line')
}
async ArcToBufferGeometry(obj, scale = true) {
/**
* Old implementation
*/
// const radius = obj.radius
// const curve = new THREE.EllipseCurve(
// 0,
// 0, // ax, aY
// radius,
// radius, // xRadius, yRadius
// obj.startAngle,
// obj.endAngle, // aStartAngle, aEndAngle
// false, // aClockwise
// 0 // aRotation
// )
// const points = curve.getPoints(50);
// const t = this.PlaneToMatrix4(obj.plane, scale);
// const geometry = new THREE.BufferGeometry()
// .setFromPoints(points)
// .applyMatrix4(t)
// return new ObjectWrapper(geometry, obj, 'line')
/**
* New implementation, a bit verbose, but it's more clear this way.
*/
/**
* ARC
*/
async ArcToGeometryData(obj, scale = true) {
const origin = new Vector3(
obj.plane.origin.x,
obj.plane.origin.y,
@@ -649,7 +744,7 @@ export default class Coverter {
const angle = Math.acos(dot)
const radius = obj.radius
// We draw the arc in a local un-rotated coordinate system. We rotate it later on via transformation
const curve = new THREE.EllipseCurve(
const curve = new EllipseCurve(
0,
0, // ax, aY
radius,
@@ -674,32 +769,43 @@ export default class Coverter {
if (scale) {
const S = new Matrix4().scale(
new THREE.Vector3(conversionFactor, conversionFactor, conversionFactor)
new Vector3(conversionFactor, conversionFactor, conversionFactor)
)
matrix.multiply(S)
}
const geometry = new THREE.BufferGeometry()
.setFromPoints(points)
.applyMatrix4(matrix)
return {
attributes: {
POSITION: this.FlattenVector3Array(points)
},
bakeTransform: matrix,
transform: null
} as GeometryData
}
async ArcToBufferGeometry(obj, scale = true) {
const geometry = Geometry.makeLineGeometry(await this.ArcToGeometryData(obj, scale))
return new ObjectWrapper(geometry, obj, 'line')
}
async EllipseToBufferGeometry(obj, scale = true) {
/**
* ELLIPSE
*/
async EllipseToGeometryData(obj, scale = true) {
const conversionFactor = scale ? getConversionFactor(obj.units) : 1
const center = new THREE.Vector3(
const center = new Vector3(
obj.plane.origin.x,
obj.plane.origin.y,
obj.plane.origin.z
).multiplyScalar(conversionFactor)
const xAxis = new THREE.Vector3(
const xAxis = new Vector3(
obj.plane.xdir.x,
obj.plane.xdir.y,
obj.plane.xdir.z
).normalize()
const yAxis = new THREE.Vector3(
const yAxis = new Vector3(
obj.plane.ydir.x,
obj.plane.ydir.y,
obj.plane.ydir.z
@@ -713,19 +819,34 @@ export default class Coverter {
const t = (index * Math.PI * 2) / resolution
const x = Math.cos(t) * obj.firstRadius * conversionFactor
const y = Math.sin(t) * obj.secondRadius * conversionFactor
const xMove = new THREE.Vector3(xAxis.x * x, xAxis.y * x, xAxis.z * x)
const yMove = new THREE.Vector3(yAxis.x * y, yAxis.y * y, yAxis.z * y)
const xMove = new Vector3(xAxis.x * x, xAxis.y * x, xAxis.z * x)
const yMove = new Vector3(yAxis.x * y, yAxis.y * y, yAxis.z * y)
const pt = new THREE.Vector3().addVectors(xMove, yMove).add(center)
const pt = new Vector3().addVectors(xMove, yMove).add(center)
points.push(pt)
}
const geometry = new THREE.BufferGeometry().setFromPoints(points)
return {
attributes: {
POSITION: this.FlattenVector3Array(points)
},
bakeTransform: null,
transform: null
} as GeometryData
}
async EllipseToBufferGeometry(obj, scale = true) {
const geometry = Geometry.makeLineGeometry(
await this.EllipseToGeometryData(obj, scale)
)
return new ObjectWrapper(geometry, obj, 'line')
}
/**
* UTILS
*/
PlaneToMatrix4(plane, scale = true) {
const m = new THREE.Matrix4()
const m = new Matrix4()
const conversionFactor = scale ? getConversionFactor(plane.units) : 1
m.makeBasis(
@@ -738,7 +859,7 @@ export default class Coverter {
* I think scaling should be done first.
*/
if (scale) {
m.scale(new THREE.Vector3(conversionFactor, conversionFactor, conversionFactor))
m.scale(new Vector3(conversionFactor, conversionFactor, conversionFactor))
}
return m
}
@@ -769,10 +890,10 @@ export default class Coverter {
const t = startAngle + (index * (endAngle - startAngle)) / resolution
const x = Math.cos(t) * radius
const y = Math.sin(t) * radius
const xMove = new THREE.Vector3(xAxis.x * x, xAxis.y * x, xAxis.z * x)
const yMove = new THREE.Vector3(yAxis.x * y, yAxis.y * y, yAxis.z * y)
const xMove = new Vector3(xAxis.x * x, xAxis.y * x, xAxis.z * x)
const yMove = new Vector3(yAxis.x * y, yAxis.y * y, yAxis.z * y)
const pt = new THREE.Vector3().addVectors(xMove, yMove).add(center)
const pt = new Vector3().addVectors(xMove, yMove).add(center)
points.push(pt)
}
return points
@@ -783,14 +904,14 @@ export default class Coverter {
let v = null
if (obj.value) {
// Old point format based on value list
v = new THREE.Vector3(
v = new Vector3(
obj.value[0] * conversionFactor,
obj.value[1] * conversionFactor,
obj.value[2] * conversionFactor
)
} else {
// New point format based on cartesian coords
v = new THREE.Vector3(
v = new Vector3(
obj.x * conversionFactor,
obj.y * conversionFactor,
obj.z * conversionFactor
@@ -798,4 +919,24 @@ export default class Coverter {
}
return v
}
PointToFloatArray(obj) {
if (obj.value) {
return [obj.value[0], obj.value[1], obj.value[2]]
} else {
return [obj.x, obj.y, obj.z]
}
}
FlattenVector3Array(input: Vector3[] | Vector2[]): number[] {
const output = new Array(input.length * 3)
const vBuff = []
for (let k = 0, l = 0; k < input.length; k++, l += 3) {
input[k].toArray(vBuff)
output[l] = vBuff[0]
output[l + 1] = vBuff[1]
output[l + 2] = vBuff[2] ? vBuff[2] : 0
}
return output
}
}
@@ -0,0 +1,426 @@
/* eslint-disable camelcase */
import {
Box3,
BufferGeometry,
Float32BufferAttribute,
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
Matrix4,
Uint16BufferAttribute,
Uint32BufferAttribute,
Vector3
} from 'three'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
import { World } from '../World'
import ObjectWrapper from './ObjectWrapper'
export enum GeometryAttributes {
POSITION = 'POSITION',
COLOR = 'COLOR',
NORMAL = 'NORMAL',
UV = 'UV',
TANGENTS = 'TANGENTS',
INDEX = 'INDEX'
}
export interface GeometryData {
attributes: Partial<Record<GeometryAttributes, number[]>>
bakeTransform: Matrix4
transform: Matrix4
}
/**
* Implementation here will change once we start working on proper batching
*/
export class Geometry {
private static _USE_RTE = true
private static _THICK_LINES = true
static get USE_RTE(): boolean {
return Geometry._USE_RTE
}
static set USE_RTE(value: boolean) {
Geometry._USE_RTE = value
console.warn(`RTE RENDERING IS NOW ${Geometry._USE_RTE}`)
}
static get THICK_LINES(): boolean {
return Geometry._THICK_LINES
}
static set THICK_LINES(value: boolean) {
Geometry._THICK_LINES = value
console.warn(`THICK_LINES IS NOW ${Geometry._THICK_LINES}`)
}
static makePointGeometry(geometryData: GeometryData): BufferGeometry {
const geometry = Geometry.makeMeshGeometry(geometryData)
World.expandWorld(geometry.boundingBox)
return geometry
}
static makePointCloudGeometry(geometryData: GeometryData): BufferGeometry {
const geometry = Geometry.makeMeshGeometry(geometryData)
World.expandWorld(geometry.boundingBox)
return geometry
}
static makeMeshGeometry(geometryData: GeometryData): BufferGeometry {
if (geometryData.bakeTransform) {
Geometry.transformGeometryData(geometryData, geometryData.bakeTransform)
}
const geometry = new BufferGeometry()
if (geometryData.attributes.INDEX) {
if (
geometryData.attributes.POSITION.length >= 65535 ||
geometryData.attributes.INDEX.length >= 65535
) {
geometry.setIndex(new Uint32BufferAttribute(geometryData.attributes.INDEX, 1))
} else {
geometry.setIndex(new Uint16BufferAttribute(geometryData.attributes.INDEX, 1))
}
}
if (geometryData.attributes.POSITION) {
geometry.setAttribute(
'position',
new Float32BufferAttribute(geometryData.attributes.POSITION, 3)
)
}
if (geometryData.attributes.COLOR) {
geometry.setAttribute(
'color',
new Float32BufferAttribute(geometryData.attributes.COLOR, 3)
)
}
geometry.computeVertexNormals()
geometry.computeBoundingSphere()
geometry.computeBoundingBox()
World.expandWorld(geometry.boundingBox)
if (Geometry.USE_RTE) {
Geometry.updateRTEGeometry(geometry)
}
return geometry
}
static makeLineGeometry(geometryData: GeometryData) {
if (geometryData.bakeTransform) {
Geometry.transformGeometryData(geometryData, geometryData.bakeTransform)
}
let geometry: { boundingBox: Box3 }
if (Geometry.THICK_LINES) {
geometry = this.makeLineGeometryTriangle(geometryData)
} else {
geometry = this.makeLineGeometryLine(geometryData)
}
World.expandWorld(geometry.boundingBox)
return geometry
}
static makeLineGeometryLine(geometryData: GeometryData) {
const geometry = new BufferGeometry()
if (geometryData.attributes.POSITION) {
geometry.setAttribute(
'position',
new Float32BufferAttribute(geometryData.attributes.POSITION, 3)
)
}
geometry.computeBoundingBox()
if (Geometry.USE_RTE) {
Geometry.updateRTEGeometry(geometry)
}
return geometry
}
static makeLineGeometryTriangle(geometryData: GeometryData) {
const geometry = new LineGeometry()
geometry.setPositions(geometryData.attributes.POSITION)
if (geometryData.attributes.COLOR) geometry.setColors(geometryData.attributes.COLOR)
geometry.computeBoundingBox()
if (Geometry.USE_RTE) {
Geometry.updateRTEGeometry(geometry)
}
return geometry
}
/**
*
* @param geometry TEMPORARY!!!
*/
public static updateRTEGeometry(geometry: BufferGeometry) {
if (Geometry.USE_RTE) {
if (geometry.type === 'BufferGeometry') {
const position_low = new Float32Array(geometry.attributes.position.array.length)
const position_high = new Float32Array(
geometry.attributes.position.array.length
)
Geometry.DoubleToHighLowBuffer(
geometry.attributes.position.array,
position_low,
position_high
)
geometry.setAttribute(
'position_low',
new Float32BufferAttribute(position_low, 3)
)
geometry.setAttribute(
'position_high',
new Float32BufferAttribute(position_high, 3)
)
} else if (geometry.type === 'LineGeometry') {
const position_low = new Float32Array(
geometry.attributes.instanceStart.array.length
)
const position_high = new Float32Array(
geometry.attributes.instanceStart.array.length
)
Geometry.DoubleToHighLowBuffer(
geometry.attributes.instanceStart.array,
position_low,
position_high
)
const instanceBufferLow = new InstancedInterleavedBuffer(
new Float32Array(position_low),
6,
1
) // xyz, xyz
geometry.setAttribute(
'instanceStartLow',
new InterleavedBufferAttribute(instanceBufferLow, 3, 0)
) // xyz
geometry.setAttribute(
'instanceEndLow',
new InterleavedBufferAttribute(instanceBufferLow, 3, 3)
) // xyz
const instanceBufferHigh = new InstancedInterleavedBuffer(
new Float32Array(position_high),
6,
1
) // xyz, xyz
geometry.setAttribute(
'instanceStartHigh',
new InterleavedBufferAttribute(instanceBufferHigh, 3, 0)
) // xyz
geometry.setAttribute(
'instanceEndHigh',
new InterleavedBufferAttribute(instanceBufferHigh, 3, 3)
) // xyz
}
}
}
static mergeGeometryAttribute(
attributes: number[][],
target: Float32Array
): ArrayLike<number> {
let offset = 0
for (let k = 0; k < attributes.length; k++) {
target.set(attributes[k], offset)
offset += attributes[k].length
}
return target
}
static mergeIndexAttribute(
indexAttributes: number[][],
positionAttributes: number[][]
): number[] {
let indexOffset = 0
const mergedIndex = []
for (let i = 0; i < indexAttributes.length; ++i) {
const index = indexAttributes[i]
for (let j = 0; j < index.length; ++j) {
mergedIndex.push(index[j] + indexOffset)
}
indexOffset += positionAttributes.length
}
return mergedIndex
}
static mergeGeometryData(geometries: GeometryData[]): GeometryData {
const sampleAttributes = geometries[0].attributes
const mergedGeometry = {
attributes: {},
bakeTransform: null,
transform: null
} as GeometryData
for (let i = 0; i < geometries.length; i++) {
if (geometries[i].bakeTransform)
Geometry.transformGeometryData(geometries[i], geometries[i].bakeTransform)
}
if (sampleAttributes[GeometryAttributes.INDEX]) {
const indexAttributes = geometries.map(
(item) => item.attributes[GeometryAttributes.INDEX]
)
const positionAttributes = geometries.map(
(item) => item.attributes[GeometryAttributes.POSITION]
)
mergedGeometry.attributes[GeometryAttributes.INDEX] =
Geometry.mergeIndexAttribute(indexAttributes, positionAttributes)
}
for (const k in sampleAttributes) {
if (k !== GeometryAttributes.INDEX) {
const attributes = geometries.map((item) => {
return item.attributes[k]
})
mergedGeometry.attributes[k] = Geometry.mergeGeometryAttribute(
attributes,
new Float32Array(attributes.reduce((prev, cur) => prev + cur.length, 0))
)
}
}
geometries.forEach((geometry) => {
for (const k in geometry.attributes) {
delete geometry.attributes[k]
}
})
return mergedGeometry
}
/**
*
* @param wrappers TEMPORARY!!!
* @returns
*/
public static applyWorldTransform(wrappers: Array<ObjectWrapper>) {
const worldCenter = World.worldBox.getCenter(new Vector3())
worldCenter.negate()
const transform = new Matrix4().setPosition(worldCenter)
World.worldBox.makeEmpty()
for (let k = 0; k < wrappers.length; k++) {
const wrapper = wrappers[k]
if (Array.isArray(wrapper.bufferGeometry)) {
Geometry.applyWorldTransform(wrapper.bufferGeometry)
return
}
try {
wrapper.bufferGeometry.applyMatrix4(transform)
wrapper.bufferGeometry.computeBoundingBox()
World.expandWorld(wrapper.bufferGeometry.boundingBox)
Geometry.updateRTEGeometry(wrapper.bufferGeometry)
} catch (e) {
console.warn(e)
}
}
}
public static transformGeometryData(geometryData: GeometryData, m: Matrix4) {
if (!geometryData.attributes.POSITION) return
const e = m.elements
for (let k = 0; k < geometryData.attributes.POSITION.length; k += 3) {
const x = geometryData.attributes.POSITION[k],
y = geometryData.attributes.POSITION[k + 1],
z = geometryData.attributes.POSITION[k + 2]
const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15])
geometryData.attributes.POSITION[k] = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w
geometryData.attributes.POSITION[k + 1] =
(e[1] * x + e[5] * y + e[9] * z + e[13]) * w
geometryData.attributes.POSITION[k + 2] =
(e[2] * x + e[6] * y + e[10] * z + e[14]) * w
}
}
public static unpackColors(int32Colors: number[]): number[] {
const colors = new Array<number>(int32Colors.length * 3)
for (let i = 0; i < int32Colors.length; i++) {
const color = int32Colors[i]
const r = (color >> 16) & 0xff
const g = (color >> 8) & 0xff
const b = color & 0xff
colors[i * 3] = r / 255
colors[i * 3 + 1] = g / 255
colors[i * 3 + 2] = b / 255
}
return colors
}
public static DoubleToHighLowVector(input: Vector3, low: Vector3, high: Vector3) {
let doubleValue = input.x
if (doubleValue >= 0.0) {
const doubleHigh = Math.floor(doubleValue / 65536.0) * 65536.0
high.x = doubleHigh
low.x = doubleValue - doubleHigh
} else {
const doubleHigh = Math.floor(-doubleValue / 65536.0) * 65536.0
high.x = -doubleHigh
low.x = doubleValue + doubleHigh
}
doubleValue = input.y
if (doubleValue >= 0.0) {
const doubleHigh = Math.floor(doubleValue / 65536.0) * 65536.0
high.y = doubleHigh
low.y = doubleValue - doubleHigh
} else {
const doubleHigh = Math.floor(-doubleValue / 65536.0) * 65536.0
high.y = -doubleHigh
low.y = doubleValue + doubleHigh
}
doubleValue = input.z
if (doubleValue >= 0.0) {
const doubleHigh = Math.floor(doubleValue / 65536.0) * 65536.0
high.z = doubleHigh
low.z = doubleValue - doubleHigh
} else {
const doubleHigh = Math.floor(-doubleValue / 65536.0) * 65536.0
high.z = -doubleHigh
low.z = doubleValue + doubleHigh
}
}
public static DoubleToHighLowBuffer(
input: ArrayLike<number>,
position_low: number[] | Float32Array,
position_high: number[] | Float32Array
) {
for (let k = 0; k < input.length; k++) {
const doubleValue = input[k]
if (doubleValue >= 0.0) {
const doubleHigh = Math.floor(doubleValue / 65536.0) * 65536.0
position_high[k] = doubleHigh
position_low[k] = doubleValue - doubleHigh
} else {
const doubleHigh = Math.floor(-doubleValue / 65536.0) * 65536.0
position_high[k] = -doubleHigh
position_low[k] = doubleValue + doubleHigh
}
}
}
public static InterleaveBuffers(
input0: number[] | Float32Array,
input1: number[] | Float32Array
) {
const out = new Array<number>(input0.length + input1.length)
for (let k = 0, l = 0; k < out.length; k += 6, l += 3) {
out[k] = input0[k]
out[k + 1] = input0[k + 1]
out[k + 2] = input0[k + 2]
out[k + 3] = input1[k]
out[k + 4] = input1[k + 1]
out[k + 5] = input1[k + 2]
}
return out
}
}
@@ -0,0 +1,100 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable camelcase */
import { speckle_basic_vert } from './shaders/speckle-basic-vert'
import { speckle_basic_frag } from './shaders/speckle-basic-frag'
import { UniformsUtils, ShaderLib, Vector3, MeshBasicMaterial } from 'three'
import { Matrix4 } from 'three'
import { Geometry } from '../converter/Geometry'
class SpeckleBasicMaterial extends MeshBasicMaterial {
private static readonly matBuff: Matrix4 = new Matrix4()
private static readonly vecBuff0: Vector3 = new Vector3()
private static readonly vecBuff1: Vector3 = new Vector3()
private static readonly vecBuff2: Vector3 = new Vector3()
constructor(parameters, defines = []) {
super(parameters)
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
;(this as any).vertProgram = speckle_basic_vert
;(this as any).fragProgram = speckle_basic_frag
;(this as any).uniforms = UniformsUtils.merge([
ShaderLib.standard.uniforms,
{
uViewer_high: {
value: this.userData.uViewer_high.value
},
uViewer_low: {
value: this.userData.uViewer_low.value
}
}
])
this.onBeforeCompile = function (shader) {
shader.uniforms.uViewer_high = this.userData.uViewer_high
shader.uniforms.uViewer_low = this.userData.uViewer_low
shader.vertexShader = this.vertProgram
shader.fragmentShader = this.fragProgram
}
if (defines) {
this.defines = {}
}
for (let k = 0; k < defines.length; k++) {
this.defines[defines[k]] = ' '
}
}
copy(source) {
super.copy(source)
this.userData = {}
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
if (Geometry.USE_RTE) {
this.defines['USE_RTE'] = ' '
}
return this
}
onBeforeRender(_this, scene, camera, geometry, object, group) {
if (Geometry.USE_RTE) {
SpeckleBasicMaterial.matBuff.copy(camera.matrixWorldInverse)
SpeckleBasicMaterial.matBuff.elements[12] = 0
SpeckleBasicMaterial.matBuff.elements[13] = 0
SpeckleBasicMaterial.matBuff.elements[14] = 0
SpeckleBasicMaterial.matBuff.multiply(object.matrixWorld)
object.modelViewMatrix.copy(SpeckleBasicMaterial.matBuff)
SpeckleBasicMaterial.vecBuff0.set(
camera.matrixWorld.elements[12],
camera.matrixWorld.elements[13],
camera.matrixWorld.elements[14]
)
Geometry.DoubleToHighLowVector(
SpeckleBasicMaterial.vecBuff0,
SpeckleBasicMaterial.vecBuff1,
SpeckleBasicMaterial.vecBuff2
)
this.userData.uViewer_low.value.copy(SpeckleBasicMaterial.vecBuff1)
this.userData.uViewer_high.value.copy(SpeckleBasicMaterial.vecBuff2)
this.needsUpdate = true
}
}
}
export default SpeckleBasicMaterial
@@ -0,0 +1,100 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable camelcase */
import { speckle_lambert_vert } from './shaders/speckle-lambert-vert'
import { speckle_lambert_frag } from './shaders/speckle-lambert-frag'
import { UniformsUtils, ShaderLib, Vector3, MeshLambertMaterial } from 'three'
import { Matrix4 } from 'three'
import { Geometry } from '../converter/Geometry'
class SpeckleLambertMaterial extends MeshLambertMaterial {
private static readonly matBuff: Matrix4 = new Matrix4()
private static readonly vecBuff0: Vector3 = new Vector3()
private static readonly vecBuff1: Vector3 = new Vector3()
private static readonly vecBuff2: Vector3 = new Vector3()
constructor(parameters, defines = []) {
super(parameters)
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
;(this as any).vertProgram = speckle_lambert_vert
;(this as any).fragProgram = speckle_lambert_frag
;(this as any).uniforms = UniformsUtils.merge([
ShaderLib.standard.uniforms,
{
uViewer_high: {
value: this.userData.uViewer_high.value
},
uViewer_low: {
value: this.userData.uViewer_low.value
}
}
])
this.onBeforeCompile = function (shader) {
shader.uniforms.uViewer_high = this.userData.uViewer_high
shader.uniforms.uViewer_low = this.userData.uViewer_low
shader.vertexShader = this.vertProgram
shader.fragmentShader = this.fragProgram
}
if (defines) {
this.defines = {}
}
for (let k = 0; k < defines.length; k++) {
this.defines[defines[k]] = ''
}
}
copy(source) {
super.copy(source)
this.userData = {}
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
if (Geometry.USE_RTE) {
this.defines['USE_RTE'] = ' '
}
return this
}
onBeforeRender(_this, scene, camera, geometry, object, group) {
if (Geometry.USE_RTE) {
SpeckleLambertMaterial.matBuff.copy(camera.matrixWorldInverse)
SpeckleLambertMaterial.matBuff.elements[12] = 0
SpeckleLambertMaterial.matBuff.elements[13] = 0
SpeckleLambertMaterial.matBuff.elements[14] = 0
SpeckleLambertMaterial.matBuff.multiply(object.matrixWorld)
object.modelViewMatrix.copy(SpeckleLambertMaterial.matBuff)
SpeckleLambertMaterial.vecBuff0.set(
camera.matrixWorld.elements[12],
camera.matrixWorld.elements[13],
camera.matrixWorld.elements[14]
)
Geometry.DoubleToHighLowVector(
SpeckleLambertMaterial.vecBuff0,
SpeckleLambertMaterial.vecBuff1,
SpeckleLambertMaterial.vecBuff2
)
this.userData.uViewer_low.value.copy(SpeckleLambertMaterial.vecBuff1)
this.userData.uViewer_high.value.copy(SpeckleLambertMaterial.vecBuff2)
this.needsUpdate = true
}
}
}
export default SpeckleLambertMaterial
@@ -0,0 +1,97 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable camelcase */
import { speckle_line_basic_vert } from './shaders/speckle-line-basic-vert'
import { speckle_line_basic_frag } from './shaders/speckle-line-basic-frag'
import { UniformsUtils, ShaderLib, Vector3, LineBasicMaterial } from 'three'
import { Matrix4 } from 'three'
import { Geometry } from '../converter/Geometry'
class SpeckleLineBasicMaterial extends LineBasicMaterial {
private static readonly matBuff: Matrix4 = new Matrix4()
private static readonly vecBuff0: Vector3 = new Vector3()
private static readonly vecBuff1: Vector3 = new Vector3()
private static readonly vecBuff2: Vector3 = new Vector3()
constructor(parameters, defines = []) {
super(parameters)
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
;(this as any).vertProgram = speckle_line_basic_vert
;(this as any).fragProgram = speckle_line_basic_frag
;(this as any).uniforms = UniformsUtils.merge([
ShaderLib.line.uniforms,
{
uViewer_high: {
value: this.userData.uViewer_high.value
},
uViewer_low: {
value: this.userData.uViewer_low.value
}
}
])
this.onBeforeCompile = function (shader) {
shader.uniforms.uViewer_high = this.userData.uViewer_high
shader.uniforms.uViewer_low = this.userData.uViewer_low
shader.vertexShader = this.vertProgram
shader.fragmentShader = this.fragProgram
}
for (let k = 0; k < defines.length; k++) {
this.defines[defines[k]] = ''
}
if (Geometry.USE_RTE) {
this.defines = {}
this.defines['USE_RTE'] = ' '
}
}
copy(source) {
super.copy(source)
this.userData = {}
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
return this
}
onBeforeRender(_this, scene, camera, geometry, object, group) {
if (Geometry.USE_RTE) {
SpeckleLineBasicMaterial.matBuff.copy(camera.matrixWorldInverse)
SpeckleLineBasicMaterial.matBuff.elements[12] = 0
SpeckleLineBasicMaterial.matBuff.elements[13] = 0
SpeckleLineBasicMaterial.matBuff.elements[14] = 0
SpeckleLineBasicMaterial.matBuff.multiply(object.matrixWorld)
object.modelViewMatrix.copy(SpeckleLineBasicMaterial.matBuff)
SpeckleLineBasicMaterial.vecBuff0.set(
camera.matrixWorld.elements[12],
camera.matrixWorld.elements[13],
camera.matrixWorld.elements[14]
)
Geometry.DoubleToHighLowVector(
SpeckleLineBasicMaterial.vecBuff0,
SpeckleLineBasicMaterial.vecBuff1,
SpeckleLineBasicMaterial.vecBuff2
)
this.userData.uViewer_low.value.copy(SpeckleLineBasicMaterial.vecBuff1)
this.userData.uViewer_high.value.copy(SpeckleLineBasicMaterial.vecBuff2)
this.needsUpdate = true
}
}
}
export default SpeckleLineBasicMaterial
@@ -0,0 +1,111 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable camelcase */
import { speckle_line_vert } from './shaders/speckle-line-vert'
import { speckle_line_frag } from './shaders/speckle-line-frag'
import { UniformsUtils, ShaderLib, Vector3 } from 'three'
import { Matrix4 } from 'three'
import { Geometry } from '../converter/Geometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
class SpeckleLineMaterial extends LineMaterial {
private static readonly matBuff: Matrix4 = new Matrix4()
private static readonly vecBuff0: Vector3 = new Vector3()
private static readonly vecBuff1: Vector3 = new Vector3()
private static readonly vecBuff2: Vector3 = new Vector3()
public set pixelThreshold(value: number) {
this.userData.pixelThreshold.value = value
this.needsUpdate = true
}
constructor(parameters, defines = []) {
super(parameters)
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
this.userData.pixelThreshold = {
value: 0
}
;(this as any).vertProgram = speckle_line_vert
;(this as any).fragProgram = speckle_line_frag
;(this as any).uniforms = UniformsUtils.merge([
ShaderLib.line.uniforms,
{
uViewer_high: {
value: this.userData.uViewer_high.value
},
uViewer_low: {
value: this.userData.uViewer_low.value
},
pixelThreshold: {
value: this.userData.pixelThreshold
}
}
])
this.onBeforeCompile = function (shader) {
shader.uniforms.uViewer_high = this.userData.uViewer_high
shader.uniforms.uViewer_low = this.userData.uViewer_low
shader.uniforms.pixelThreshold = this.userData.pixelThreshold
shader.vertexShader = this.vertProgram
shader.fragmentShader = this.fragProgram
}
for (let k = 0; k < defines.length; k++) {
this.defines[defines[k]] = ''
}
if (Geometry.USE_RTE) {
this.defines['USE_RTE'] = ' '
}
}
copy(source) {
super.copy(source)
this.userData = {}
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
this.userData.pixelThreshold = {
value: source.userData.pixelThreshold.value
}
return this
}
onBeforeRender(_this, scene, camera, geometry, object, group) {
if (Geometry.USE_RTE) {
SpeckleLineMaterial.matBuff.copy(camera.matrixWorldInverse)
SpeckleLineMaterial.matBuff.elements[12] = 0
SpeckleLineMaterial.matBuff.elements[13] = 0
SpeckleLineMaterial.matBuff.elements[14] = 0
SpeckleLineMaterial.matBuff.multiply(object.matrixWorld)
object.modelViewMatrix.copy(SpeckleLineMaterial.matBuff)
SpeckleLineMaterial.vecBuff0.set(
camera.matrixWorld.elements[12],
camera.matrixWorld.elements[13],
camera.matrixWorld.elements[14]
)
Geometry.DoubleToHighLowVector(
SpeckleLineMaterial.vecBuff0,
SpeckleLineMaterial.vecBuff1,
SpeckleLineMaterial.vecBuff2
)
this.userData.uViewer_low.value.copy(SpeckleLineMaterial.vecBuff1)
this.userData.uViewer_high.value.copy(SpeckleLineMaterial.vecBuff2)
this.needsUpdate = true
}
}
}
export default SpeckleLineMaterial
@@ -0,0 +1,100 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable camelcase */
import { speckle_standard_vert } from './shaders/speckle-standard-vert'
import { speckle_standard_frag } from './shaders/speckle-standard-frag'
import { UniformsUtils, ShaderLib, Vector3, MeshStandardMaterial } from 'three'
import { Matrix4 } from 'three'
import { Geometry } from '../converter/Geometry'
class SpeckleStandardMaterial extends MeshStandardMaterial {
private static readonly matBuff: Matrix4 = new Matrix4()
private static readonly vecBuff0: Vector3 = new Vector3()
private static readonly vecBuff1: Vector3 = new Vector3()
private static readonly vecBuff2: Vector3 = new Vector3()
constructor(parameters, defines = []) {
super(parameters)
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
;(this as any).vertProgram = speckle_standard_vert
;(this as any).fragProgram = speckle_standard_frag
;(this as any).uniforms = UniformsUtils.merge([
ShaderLib.standard.uniforms,
{
uViewer_high: {
value: this.userData.uViewer_high.value
},
uViewer_low: {
value: this.userData.uViewer_low.value
}
}
])
this.onBeforeCompile = function (shader) {
shader.uniforms.uViewer_high = this.userData.uViewer_high
shader.uniforms.uViewer_low = this.userData.uViewer_low
shader.vertexShader = this.vertProgram
shader.fragmentShader = this.fragProgram
}
if (defines) {
this.defines = {}
}
for (let k = 0; k < defines.length; k++) {
this.defines[defines[k]] = ' '
}
}
copy(source) {
super.copy(source)
this.userData = {}
this.userData.uViewer_high = {
value: new Vector3()
}
this.userData.uViewer_low = {
value: new Vector3()
}
if (Geometry.USE_RTE) {
this.defines['USE_RTE'] = ' '
}
return this
}
onBeforeRender(_this, scene, camera, geometry, object, group) {
if (Geometry.USE_RTE) {
SpeckleStandardMaterial.matBuff.copy(camera.matrixWorldInverse)
SpeckleStandardMaterial.matBuff.elements[12] = 0
SpeckleStandardMaterial.matBuff.elements[13] = 0
SpeckleStandardMaterial.matBuff.elements[14] = 0
SpeckleStandardMaterial.matBuff.multiply(object.matrixWorld)
object.modelViewMatrix.copy(SpeckleStandardMaterial.matBuff)
SpeckleStandardMaterial.vecBuff0.set(
camera.matrixWorld.elements[12],
camera.matrixWorld.elements[13],
camera.matrixWorld.elements[14]
)
Geometry.DoubleToHighLowVector(
SpeckleStandardMaterial.vecBuff0,
SpeckleStandardMaterial.vecBuff1,
SpeckleStandardMaterial.vecBuff2
)
this.userData.uViewer_low.value.copy(SpeckleStandardMaterial.vecBuff1)
this.userData.uViewer_high.value.copy(SpeckleStandardMaterial.vecBuff2)
this.needsUpdate = true
}
}
}
export default SpeckleStandardMaterial
@@ -0,0 +1,53 @@
export const speckle_basic_frag = /* glsl */ `
uniform vec3 diffuse;
uniform float opacity;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
#include <common>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <alphatest_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <envmap_common_pars_fragment>
#include <envmap_pars_fragment>
#include <cube_uv_reflection_fragment>
#include <fog_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
vec4 diffuseColor = vec4( diffuse, opacity );
#include <logdepthbuf_fragment>
#include <map_fragment>
#include <color_fragment>
#include <alphamap_fragment>
#include <alphatest_fragment>
#include <specularmap_fragment>
ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
// accumulation (baked indirect lighting only)
#ifdef USE_LIGHTMAP
vec4 lightMapTexel = texture2D( lightMap, vUv2 );
reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;
#else
reflectedLight.indirectDiffuse += vec3( 1.0 );
#endif
// modulation
#include <aomap_fragment>
reflectedLight.indirectDiffuse *= diffuseColor.rgb;
vec3 outgoingLight = reflectedLight.indirectDiffuse;
#include <envmap_fragment>
#include <output_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
#include <dithering_fragment>
}
`
@@ -0,0 +1,56 @@
export const speckle_basic_vert = /* glsl */ `
#include <common>
#ifdef USE_RTE
attribute vec3 position_high;
attribute vec3 position_low;
uniform vec3 uViewer_high;
uniform vec3 uViewer_low;
#endif
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <morphcolor_vertex>
#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#endif
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
// #include <project_vertex> COMMENTED CHUNK
#ifdef USE_RTE
vec3 highDifference = vec3(position_high.xyz - uViewer_high);
vec3 lowDifference = vec3(position_low.xyz - uViewer_low);
vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.);
#else
vec4 mvPosition = vec4( transformed, 1.0 );
#endif
#ifdef USE_INSTANCING
mvPosition = instanceMatrix * mvPosition;
#endif
mvPosition = modelViewMatrix * mvPosition;
gl_Position = projectionMatrix * mvPosition;
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <worldpos_vertex>
#include <envmap_vertex>
#include <fog_vertex>
}
`
@@ -0,0 +1,71 @@
export const speckle_lambert_frag = /* glsl */ `
uniform vec3 diffuse;
uniform vec3 emissive;
uniform float opacity;
varying vec3 vLightFront;
varying vec3 vIndirectFront;
#ifdef DOUBLE_SIDED
varying vec3 vLightBack;
varying vec3 vIndirectBack;
#endif
#include <common>
#include <packing>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <alphatest_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <emissivemap_pars_fragment>
#include <envmap_common_pars_fragment>
#include <envmap_pars_fragment>
#include <cube_uv_reflection_fragment>
#include <bsdfs>
#include <lights_pars_begin>
#include <fog_pars_fragment>
#include <shadowmap_pars_fragment>
#include <shadowmask_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
vec4 diffuseColor = vec4( diffuse, opacity );
ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
vec3 totalEmissiveRadiance = emissive;
#include <logdepthbuf_fragment>
#include <map_fragment>
#include <color_fragment>
#include <alphamap_fragment>
#include <alphatest_fragment>
#include <specularmap_fragment>
#include <emissivemap_fragment>
// accumulation
#ifdef DOUBLE_SIDED
reflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;
#else
reflectedLight.indirectDiffuse += vIndirectFront;
#endif
#include <lightmap_fragment>
reflectedLight.indirectDiffuse *= BRDF_Lambert( diffuseColor.rgb );
#ifdef DOUBLE_SIDED
reflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;
#else
reflectedLight.directDiffuse = vLightFront;
#endif
reflectedLight.directDiffuse *= BRDF_Lambert( diffuseColor.rgb ) * getShadowMask();
// modulation
#include <aomap_fragment>
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;
#include <envmap_fragment>
#include <output_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
#include <dithering_fragment>
}
`
@@ -0,0 +1,67 @@
export const speckle_lambert_vert = /* glsl */ `
#define LAMBERT
#ifdef USE_RTE
attribute vec3 position_high;
attribute vec3 position_low;
uniform vec3 uViewer_high;
uniform vec3 uViewer_low;
#endif
varying vec3 vLightFront;
varying vec3 vIndirectFront;
#ifdef DOUBLE_SIDED
varying vec3 vLightBack;
varying vec3 vIndirectBack;
#endif
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <bsdfs>
#include <lights_pars_begin>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <morphcolor_vertex>
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
//#include <project_vertex> COMMENTED CHUNK
#ifdef USE_RTE
vec3 highDifference = vec3(position_high.xyz - uViewer_high);
vec3 lowDifference = vec3(position_low.xyz - uViewer_low);
vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.);
#else
vec4 mvPosition = vec4( transformed, 1.0 );
#endif
#ifdef USE_INSTANCING
mvPosition = instanceMatrix * mvPosition;
#endif
mvPosition = modelViewMatrix * mvPosition;
gl_Position = projectionMatrix * mvPosition;
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <worldpos_vertex>
#include <envmap_vertex>
#include <lights_lambert_vertex>
#include <shadowmap_vertex>
#include <fog_vertex>
}
`
@@ -0,0 +1,53 @@
export const speckle_line_basic_frag = /* glsl */ `
uniform vec3 diffuse;
uniform float opacity;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
#include <common>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <alphatest_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <envmap_common_pars_fragment>
#include <envmap_pars_fragment>
#include <cube_uv_reflection_fragment>
#include <fog_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
vec4 diffuseColor = vec4( diffuse, opacity );
#include <logdepthbuf_fragment>
#include <map_fragment>
#include <color_fragment>
#include <alphamap_fragment>
#include <alphatest_fragment>
#include <specularmap_fragment>
ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
// accumulation (baked indirect lighting only)
#ifdef USE_LIGHTMAP
vec4 lightMapTexel = texture2D( lightMap, vUv2 );
reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;
#else
reflectedLight.indirectDiffuse += vec3( 1.0 );
#endif
// modulation
#include <aomap_fragment>
reflectedLight.indirectDiffuse *= diffuseColor.rgb;
vec3 outgoingLight = reflectedLight.indirectDiffuse;
#include <envmap_fragment>
#include <output_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
#include <dithering_fragment>
}
`
@@ -0,0 +1,52 @@
export const speckle_line_basic_vert = /* glsl */ `
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
#ifdef USE_RTE
attribute vec3 position_high;
attribute vec3 position_low;
uniform vec3 uViewer_high;
uniform vec3 uViewer_low;
#endif
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <morphcolor_vertex>
#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#endif
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
// #include <project_vertex> COMMENTED CHUNK
#ifdef USE_RTE
vec3 highDifference = vec3(position_high.xyz - uViewer_high);
vec3 lowDifference = vec3(position_low.xyz - uViewer_low);
vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.);
#else
vec4 mvPosition = vec4( transformed, 1.0 );
#endif
#ifdef USE_INSTANCING
mvPosition = instanceMatrix * mvPosition;
#endif
mvPosition = modelViewMatrix * mvPosition;
gl_Position = projectionMatrix * mvPosition;
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <worldpos_vertex>
#include <envmap_vertex>
#include <fog_vertex>
}
`
@@ -0,0 +1,162 @@
export const speckle_line_frag = /* glsl */ `
uniform vec3 diffuse;
uniform float opacity;
uniform float linewidth;
// varying vec3 debugColor;
#ifdef USE_DASH
uniform float dashOffset;
uniform float dashSize;
uniform float gapSize;
#endif
varying float vLineDistance;
#ifdef WORLD_UNITS
varying vec4 worldPos;
varying vec3 worldStart;
varying vec3 worldEnd;
varying float correctedLineWidth;
#ifdef USE_DASH
varying vec2 vUv;
#endif
#else
varying vec2 vUv;
#endif
#include <common>
#include <color_pars_fragment>
#include <fog_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) {
float mua;
float mub;
vec3 p13 = p1 - p3;
vec3 p43 = p4 - p3;
vec3 p21 = p2 - p1;
float d1343 = dot( p13, p43 );
float d4321 = dot( p43, p21 );
float d1321 = dot( p13, p21 );
float d4343 = dot( p43, p43 );
float d2121 = dot( p21, p21 );
float denom = d2121 * d4343 - d4321 * d4321;
float numer = d1343 * d4321 - d1321 * d4343;
mua = numer / denom;
mua = clamp( mua, 0.0, 1.0 );
mub = ( d1343 + d4321 * ( mua ) ) / d4343;
mub = clamp( mub, 0.0, 1.0 );
return vec2( mua, mub );
}
void main() {
#include <clipping_planes_fragment>
#ifdef USE_DASH
if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps
if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX
#endif
float alpha = opacity;
#ifdef WORLD_UNITS
// Find the closest points on the view ray and the line segment
vec3 rayEnd = normalize( worldPos.xyz ) * 1e5;
vec3 lineDir = worldEnd - worldStart;
vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd );
vec3 p1 = worldStart + lineDir * params.x;
vec3 p2 = rayEnd * params.y;
vec3 delta = p1 - p2;
float len = length( delta );
float norm = len / correctedLineWidth;
#ifndef USE_DASH
#ifdef USE_ALPHA_TO_COVERAGE
float dnorm = fwidth( norm );
alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm );
#else
if ( norm > 0.5 ) {
discard;
}
#endif
#endif
#else
#ifdef USE_ALPHA_TO_COVERAGE
// artifacts appear on some hardware if a derivative is taken within a conditional
float a = vUv.x;
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
float len2 = a * a + b * b;
float dlen = fwidth( len2 );
if ( abs( vUv.y ) > 1.0 ) {
alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 );
}
#else
if ( abs( vUv.y ) > 1.0 ) {
float a = vUv.x;
float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0;
float len2 = a * a + b * b;
if ( len2 > 1.0 ) discard;
}
#endif
#endif
vec4 diffuseColor = vec4( diffuse, alpha );
#include <logdepthbuf_fragment>
#include <color_fragment>
gl_FragColor = vec4( diffuseColor.rgb, alpha );
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
}
`
@@ -0,0 +1,283 @@
export const speckle_line_vert = /* glsl */ `
#include <common>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
uniform float linewidth;
uniform vec2 resolution;
uniform float pixelThreshold;
#define SEARCH_STEPS 10
attribute vec3 instanceStart;
attribute vec3 instanceEnd;
attribute vec3 instanceColorStart;
attribute vec3 instanceColorEnd;
// varying vec3 debugColor;
#ifdef WORLD_UNITS
varying vec4 worldPos;
varying vec3 worldStart;
varying vec3 worldEnd;
varying float correctedLineWidth;
#ifdef USE_DASH
varying vec2 vUv;
#endif
#else
varying vec2 vUv;
#endif
#ifdef USE_DASH
uniform float dashScale;
attribute float instanceDistanceStart;
attribute float instanceDistanceEnd;
varying float vLineDistance;
#endif
#ifdef USE_RTE
// attribute vec3 position_high;
// attribute vec3 position_low;
attribute vec3 instanceStartLow;
attribute vec3 instanceStartHigh;
attribute vec3 instanceEndLow;
attribute vec3 instanceEndHigh;
uniform vec3 uViewer_high;
uniform vec3 uViewer_low;
#endif
void trimSegment( const in vec4 start, inout vec4 end ) {
// trim end segment so it terminates between the camera plane and the near plane
// conservative estimate of the near plane
float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column
float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column
float nearEstimate = - 0.5 * b / a;
float alpha = ( nearEstimate - start.z ) / ( end.z - start.z );
end.xyz = mix( start.xyz, end.xyz, alpha );
}
float screenSpaceDistance(vec4 p0, vec4 p1) {
p0 = projectionMatrix * p0;
p0 /= p0.w;
p1 = projectionMatrix * p1;
p1 /= p1.w;
return length(p1.xy - p0.xy);
}
void main() {
vec3 computedPosition = position;
#ifdef USE_COLOR
vColor.xyz = ( computedPosition.y < 0.5 ) ? instanceColorStart : instanceColorEnd;
#endif
#ifdef USE_DASH
vLineDistance = ( computedPosition.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd;
vUv = uv;
#endif
float aspect = resolution.x / resolution.y;
// camera space
#ifdef USE_RTE
vec3 startHighDifference = vec3(instanceStartHigh.xyz - uViewer_high);
vec3 startLowDifference = vec3(instanceStartLow.xyz - uViewer_low);
vec3 endHighDifference = vec3(instanceEndHigh.xyz - uViewer_high);
vec3 endLowDifference = vec3(instanceEndLow.xyz - uViewer_low);
vec4 start = modelViewMatrix * vec4( startLowDifference + startHighDifference, 1.0 );
vec4 end = modelViewMatrix * vec4( endLowDifference + endHighDifference, 1.0 );
#else
vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 );
vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 );
#endif
#ifdef WORLD_UNITS
worldStart = start.xyz;
worldEnd = end.xyz;
#else
vUv = uv;
#endif
// special case for perspective projection, and segments that terminate either in, or behind, the camera plane
// clearly the gpu firmware has a way of addressing this issue when projecting into ndc space
// but we need to perform ndc-space calculations in the shader, so we must address this issue directly
// perhaps there is a more elegant solution -- WestLangley
bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column
if ( perspective ) {
if ( start.z < 0.0 && end.z >= 0.0 ) {
trimSegment( start, end );
} else if ( end.z < 0.0 && start.z >= 0.0 ) {
trimSegment( end, start );
}
}
// clip space
vec4 clipStart = projectionMatrix * start;
vec4 clipEnd = projectionMatrix * end;
// ndc space
vec3 ndcStart = clipStart.xyz / clipStart.w;
vec3 ndcEnd = clipEnd.xyz / clipEnd.w;
// direction
vec2 dir = ndcEnd.xy - ndcStart.xy;
// account for clip-space aspect ratio
dir.x *= aspect;
dir = normalize( dir );
#ifdef WORLD_UNITS
// get the offset direction as perpendicular to the view vector
vec3 worldDir = normalize( end.xyz - start.xyz );
vec3 offset;
if ( computedPosition.y < 0.5 ) {
offset = normalize( cross( start.xyz, worldDir ) );
} else {
offset = normalize( cross( end.xyz, worldDir ) );
}
// sign flip
if ( computedPosition.x < 0.0 ) offset *= - 1.0;
float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) );
// don't extend the line if we're rendering dashes because we
// won't be rendering the endcaps
#ifndef USE_DASH
// extend the line bounds to encompass endcaps
start.xyz += - worldDir * linewidth * 0.5;
end.xyz += worldDir * linewidth * 0.5;
// shift the position of the quad so it hugs the forward edge of the line
offset.xy -= dir * forwardOffset;
offset.z += 0.5;
#endif
// endcaps
if ( computedPosition.y > 1.0 || computedPosition.y < 0.0 ) {
offset.xy += dir * 2.0 * forwardOffset;
}
// debugColor = vec3(0., 0., 1.);
correctedLineWidth = linewidth;
vec3 cOffset = offset;
// adjust for linewidth
offset *= linewidth * 0.5;
// set the world position
worldPos = ( computedPosition.y < 0.5 ) ? start : end;
/*
Not great, not terrible
*/
float d;
float offsetStep = linewidth;
vec3 move = offset;
float pixelSize = length(vec2(pixelThreshold/resolution.x + pixelThreshold/resolution.y));
for(int i = 0; i < SEARCH_STEPS; i++){
move = cOffset * offsetStep;
d = screenSpaceDistance(worldPos, worldPos + vec4(move, 0.));
if(d > pixelSize) {
correctedLineWidth = offsetStep;
break;
}
offsetStep += offsetStep;
}
worldPos.xyz += move;
// project the worldpos
vec4 clip = projectionMatrix * worldPos;
// shift the depth of the projected points so the line
// segments overlap neatly
vec3 clipPose = ( computedPosition.y < 0.5 ) ? ndcStart : ndcEnd;
clip.z = clipPose.z * clip.w;
#else
vec2 offset = vec2( dir.y, - dir.x );
// undo aspect ratio adjustment
dir.x /= aspect;
offset.x /= aspect;
// sign flip
if ( computedPosition.x < 0.0 ) offset *= - 1.0;
// endcaps
if ( computedPosition.y < 0.0 ) {
offset += - dir;
} else if ( computedPosition.y > 1.0 ) {
offset += dir;
}
// adjust for linewidth
offset *= linewidth;
// adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ...
offset /= resolution.y;
// select end
vec4 clip = ( computedPosition.y < 0.5 ) ? clipStart : clipEnd;
// back to clip space
offset *= clip.w;
clip.xy += offset;
#endif
gl_Position = clip;
vec4 mvPosition = ( computedPosition.y < 0.5 ) ? start : end; // this is an approximation
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <fog_vertex>
}
`
@@ -0,0 +1,147 @@
export const speckle_standard_frag = /* glsl */ `
#define STANDARD
#ifdef PHYSICAL
#define IOR
#define SPECULAR
#endif
uniform vec3 diffuse;
uniform vec3 emissive;
uniform float roughness;
uniform float metalness;
uniform float opacity;
#ifdef IOR
uniform float ior;
#endif
#ifdef SPECULAR
uniform float specularIntensity;
uniform vec3 specularColor;
#ifdef USE_SPECULARINTENSITYMAP
uniform sampler2D specularIntensityMap;
#endif
#ifdef USE_SPECULARCOLORMAP
uniform sampler2D specularColorMap;
#endif
#endif
#ifdef USE_CLEARCOAT
uniform float clearcoat;
uniform float clearcoatRoughness;
#endif
#ifdef USE_SHEEN
uniform vec3 sheenColor;
uniform float sheenRoughness;
#ifdef USE_SHEENCOLORMAP
uniform sampler2D sheenColorMap;
#endif
#ifdef USE_SHEENROUGHNESSMAP
uniform sampler2D sheenRoughnessMap;
#endif
#endif
varying vec3 vViewPosition;
#include <common>
#include <packing>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <alphatest_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <emissivemap_pars_fragment>
#include <bsdfs>
#include <cube_uv_reflection_fragment>
#include <envmap_common_pars_fragment>
#include <envmap_physical_pars_fragment>
#include <fog_pars_fragment>
#include <lights_pars_begin>
#include <normal_pars_fragment>
#include <lights_physical_pars_fragment>
#include <transmission_pars_fragment>
#include <shadowmap_pars_fragment>
#include <bumpmap_pars_fragment>
#include <normalmap_pars_fragment>
#include <clearcoat_pars_fragment>
#include <roughnessmap_pars_fragment>
#include <metalnessmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
vec4 diffuseColor = vec4( diffuse, opacity );
ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
vec3 totalEmissiveRadiance = emissive;
#include <logdepthbuf_fragment>
#include <map_fragment>
#include <color_fragment>
#include <alphamap_fragment>
#include <alphatest_fragment>
#include <roughnessmap_fragment>
#include <metalnessmap_fragment>
#include <normal_fragment_begin>
#include <normal_fragment_maps>
#include <clearcoat_normal_fragment_begin>
#include <clearcoat_normal_fragment_maps>
#include <emissivemap_fragment>
// accumulation
#include <lights_physical_fragment>
#include <lights_fragment_begin>
#include <lights_fragment_maps>
#include <lights_fragment_end>
// modulation
#include <aomap_fragment>
vec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;
vec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;
#include <transmission_fragment>
vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;
#ifdef USE_SHEEN
// Sheen energy compensation approximation calculation can be found at the end of
// https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing
float sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );
outgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;
#endif
#ifdef USE_CLEARCOAT
float dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );
vec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );
outgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;
#endif
#include <output_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
#include <dithering_fragment>
}
`
@@ -0,0 +1,84 @@
export const speckle_standard_vert = /* glsl */ `
#define STANDARD
#ifdef USE_RTE
attribute vec3 position_high;
attribute vec3 position_low;
uniform vec3 uViewer_high;
uniform vec3 uViewer_low;
#endif
varying vec3 vViewPosition;
#ifdef USE_TRANSMISSION
varying vec3 vWorldPosition;
#endif
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <displacementmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <normal_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <morphcolor_vertex>
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#include <normal_vertex>
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <displacementmap_vertex>
//#include <project_vertex> // EDITED CHUNK
#ifdef USE_RTE
vec3 highDifference = vec3(position_high.xyz - uViewer_high);
vec3 lowDifference = vec3(position_low.xyz - uViewer_low);
vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.);
#else
vec4 mvPosition = vec4( transformed, 1.0 );
#endif
#ifdef USE_INSTANCING
mvPosition = instanceMatrix * mvPosition;
#endif
mvPosition = modelViewMatrix * mvPosition;
gl_Position = projectionMatrix * mvPosition;
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
vViewPosition = - mvPosition.xyz;
#include <worldpos_vertex>
#include <shadowmap_vertex>
#include <fog_vertex>
#ifdef USE_TRANSMISSION
vWorldPosition = worldPosition.xyz;
#endif
}
`
+9
View File
@@ -0,0 +1,9 @@
declare module '*.png' {
const value: string
export default value
}
declare module '*.hdr' {
const value: string
export default value
}
+22
View File
@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es2019",
"module": "ESNext",
"lib": ["DOM"],
"moduleResolution": "Node",
"strict": false,
"sourceMap": true,
"isolatedModules": true,
"esModuleInterop": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"noImplicitReturns": false,
"skipLibCheck": true,
"outDir": "./dist",
"allowJs": true,
"checkJs": false,
"declaration": true
},
"include": ["./src/**/*"],
"exclude": ["dist"]
}
+500 -2307
View File
File diff suppressed because it is too large Load Diff