diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 4ab710f11..6f9982f24 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -7,16 +7,18 @@ export default class Sandbox { private pane: Pane private tabs: any - private static urlParams = { + public static urlParams = { url: 'https://latest.speckle.dev/streams/010b3af4c3/objects/a401baf38fe5809d0eb9d3c902a36e8f' } - private static sceneParams = { + public static sceneParams = { + worldSize: {x: 0, y: 0, z: 0}, + worldOrigin: {x:0, y:0, z:0}, exposure: 0.4, tonemapping: 'Linear' } - public constructor(viewer: IViewer) { + public constructor(viewer: Viewer) { this.viewer = viewer this.pane = new Pane({ title: 'Sandbox', expanded: true }) this.pane['containerElem_'].style.width = "300px"; @@ -30,6 +32,10 @@ export default class Sandbox { }); } + public refresh() { + this.pane.refresh(); + } + public makeGenericUI() { this.tabs.pages[0].addInput(Sandbox.urlParams, 'url', { title: 'url' @@ -76,6 +82,40 @@ export default class Sandbox { } 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" + }); + + this.tabs.pages[1].addSeparator(); const postFolder = this.tabs.pages[1].addFolder({ title: "Post", expanded: true diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index b53a1eedb..4e991bb9c 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -11,18 +11,26 @@ if (!container) { const viewer = new Viewer(container) await viewer.init() +const sandbox = new Sandbox(viewer) + window.addEventListener('load', () => { viewer.onWindowResize() }) -const sandbox = new Sandbox(viewer) -sandbox.makeGenericUI() -sandbox.makeSceneUI() -// Load demo object -sandbox.loadUrl('https://latest.speckle.dev/streams/921741b30b/commits/c5acd4ff91?c=%5B147162.48107,6396783.94314,18.1266,147140.22466,6396758.05514,9.41576,0,1%5D') 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') diff --git a/packages/viewer/src/modules/SceneObjectManager.js b/packages/viewer/src/modules/SceneObjectManager.js index 760a0a4c6..34b7bb4c2 100644 --- a/packages/viewer/src/modules/SceneObjectManager.js +++ b/packages/viewer/src/modules/SceneObjectManager.js @@ -3,9 +3,10 @@ import debounce from 'lodash.debounce' import SceneObjects from './SceneObjects' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js' import { Line2 } from 'three/examples/jsm/lines/Line2.js' -import { Vector2 } from 'three' +import { Matrix4, Vector2, Vector3 } from 'three' import { GEOMETRY_LINES_AS_TRIANGLES } from './converter/Geometry' - +import SpeckleStandardMaterial from './materials/SpeckleStandardMaterial' +import SpeckleLineMaterial from './materials/SpeckleLineMaterial' /** * Manages objects and provides some convenience methods to focus on the entire scene, or one specific object. */ @@ -17,7 +18,7 @@ export default class SceneObjectManager { this.sceneObjects = new SceneObjects(viewer) - this.solidMaterial = new THREE.MeshStandardMaterial({ + this.solidMaterial = new SpeckleStandardMaterial({ color: 0x8d9194, emissive: 0x0, roughness: 1, @@ -362,7 +363,7 @@ export default class SceneObjectManager { makeLineMaterial() { let lineMaterial if (GEOMETRY_LINES_AS_TRIANGLES) { - lineMaterial = new LineMaterial({ + lineMaterial = new SpeckleLineMaterial({ color: 0x7f7f7f, linewidth: 1, // in world units with size attenuation, pixels otherwise worldUnits: false, diff --git a/packages/viewer/src/modules/SceneObjects.js b/packages/viewer/src/modules/SceneObjects.js index 93023e246..40761d6a0 100644 --- a/packages/viewer/src/modules/SceneObjects.js +++ b/packages/viewer/src/modules/SceneObjects.js @@ -44,6 +44,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 @@ -289,7 +290,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 } diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index 654e7f6dc..c07163f18 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -9,12 +9,13 @@ import InteractionHandler from './InteractionHandler' import CameraHandler from './context/CameraHanlder' import SectionBox from './SectionBox' -import { Clock, CubeCamera } from 'three' +import { Box3, Clock, CubeCamera, 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' export class Viewer extends EventEmitter implements IViewer { private clock: Clock @@ -34,6 +35,22 @@ export class Viewer extends EventEmitter implements IViewer { public static Assets: Assets + private _worldSize: Box3 = new Box3() + 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 constructor( container: HTMLElement, params: ViewerParams = DefaultViewerParams diff --git a/packages/viewer/src/modules/ViewerObjectLoader.js b/packages/viewer/src/modules/ViewerObjectLoader.js index 902e52cf1..471f6c7bd 100644 --- a/packages/viewer/src/modules/ViewerObjectLoader.js +++ b/packages/viewer/src/modules/ViewerObjectLoader.js @@ -67,6 +67,7 @@ export default class ViewerObjectLoader { let total = 0 let viewerLoads = 0 let firstObjectPromise = null + let parsedObjects = [] // Temporary until refactor for await (const obj of this.loader.getObjectIterator()) { if (this.cancel) { this.viewer.emit('load-progress', { @@ -85,7 +86,7 @@ export default class ViewerObjectLoader { async (objectWrapper) => { await this.converter.asyncPause() objectWrapper.meta.__importedUrl = this.objectUrl - this.viewer.sceneManager.addObject(objectWrapper) + parsedObjects.push(objectWrapper) // Temporary until refactor viewerLoads++ } ) @@ -103,7 +104,13 @@ export default class ViewerObjectLoader { await firstObjectPromise } + // Temporary until refactor + for (var 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}`) diff --git a/packages/viewer/src/modules/World.ts b/packages/viewer/src/modules/World.ts new file mode 100644 index 000000000..f3f31ddca --- /dev/null +++ b/packages/viewer/src/modules/World.ts @@ -0,0 +1,28 @@ +import { Box3 } from 'three' + +export class World { + /* This will no longer exist when we have a scene tree */ + private static readonly boxes: Array = new Array() + 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 (var k = 0; k < this.boxes.length; k++) { + World.worldBox.union(World.boxes[k]) + } + } + + public static resetWorld() { + World.worldBox.makeEmpty() + } +} diff --git a/packages/viewer/src/modules/converter/Geometry.ts b/packages/viewer/src/modules/converter/Geometry.ts index aa45cbae8..7b11ad119 100644 --- a/packages/viewer/src/modules/converter/Geometry.ts +++ b/packages/viewer/src/modules/converter/Geometry.ts @@ -1,12 +1,16 @@ import { + Box3, BoxBufferGeometry, + BufferAttribute, BufferGeometry, Float32BufferAttribute, Matrix4, Uint16BufferAttribute, - Uint32BufferAttribute + Uint32BufferAttribute, + Vector3 } from 'three' import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js' +import { World } from '../World' export const GEOMETRY_LINES_AS_TRIANGLES = true export enum GeometryAttributes { @@ -29,78 +33,138 @@ export interface GeometryData { */ export class Geometry { static makePointGeometry(geometryData: GeometryData): BufferGeometry { - return Geometry.makeMeshGeometry(geometryData) + const geometry = Geometry.makeMeshGeometry(geometryData) + World.expandWorld(geometry.boundingBox) + return geometry } static makePointCloudGeometry(geometryData: GeometryData): BufferGeometry { - return Geometry.makeMeshGeometry(geometryData) + 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 buffer = new BufferGeometry() + const geometry = new BufferGeometry() if (geometryData.attributes.INDEX) { if ( geometryData.attributes.POSITION.length >= 65535 || geometryData.attributes.INDEX.length >= 65535 ) { - buffer.setIndex(new Uint32BufferAttribute(geometryData.attributes.INDEX, 1)) + geometry.setIndex(new Uint32BufferAttribute(geometryData.attributes.INDEX, 1)) } else { - buffer.setIndex(new Uint16BufferAttribute(geometryData.attributes.INDEX, 1)) + geometry.setIndex(new Uint16BufferAttribute(geometryData.attributes.INDEX, 1)) } } - + const position_low = new Float32Array(geometryData.attributes.POSITION.length) + const position_high = new Float32Array(geometryData.attributes.POSITION.length) if (geometryData.attributes.POSITION) { - buffer.setAttribute( + for (var k = 0; k < geometryData.attributes.POSITION.length; k++) { + const doubleValue = geometryData.attributes.POSITION[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 + } + } + geometry.setAttribute( 'position', new Float32BufferAttribute(geometryData.attributes.POSITION, 3) ) } if (geometryData.attributes.COLOR) { - buffer.setAttribute( + geometry.setAttribute( 'color', new Float32BufferAttribute(geometryData.attributes.COLOR, 3) ) } - buffer.computeVertexNormals() - buffer.computeBoundingSphere() + geometry.computeVertexNormals() + geometry.computeBoundingSphere() + geometry.computeBoundingBox() - return buffer + World.expandWorld(geometry.boundingBox) + + geometry.setAttribute('position_low', new Float32BufferAttribute(position_low, 3)) + geometry.setAttribute('position_high', new Float32BufferAttribute(position_high, 3)) + + return geometry } static makeLineGeometry(geometryData: GeometryData) { if (geometryData.bakeTransform) { Geometry.transformGeometryData(geometryData, geometryData.bakeTransform) } + let geometry: { boundingBox: Box3 } if (GEOMETRY_LINES_AS_TRIANGLES) { - return this.makeLineGeometry_TRIANGLE(geometryData) + geometry = this.makeLineGeometry_TRIANGLE(geometryData) } else { - return this.makeLineGeometry_LINE(geometryData) + geometry = this.makeLineGeometry_LINE(geometryData) } + World.expandWorld(geometry.boundingBox) + + return geometry } static makeLineGeometry_LINE(geometryData: GeometryData) { - const buffer = new BufferGeometry() + const geometry = new BufferGeometry() if (geometryData.attributes.POSITION) { - buffer.setAttribute( + geometry.setAttribute( 'position', new Float32BufferAttribute(geometryData.attributes.POSITION, 3) ) - - if (geometryData.bakeTransform) { - buffer.attributes.position.applyMatrix4(geometryData.bakeTransform) + } + geometry.computeBoundingBox() + const position_low = new Float32Array(geometryData.attributes.POSITION.length) + const position_high = new Float32Array(geometryData.attributes.POSITION.length) + if (geometryData.attributes.POSITION) { + for (var k = 0; k < geometryData.attributes.POSITION.length; k++) { + const doubleValue = geometryData.attributes.POSITION[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 + } } } - return buffer + geometry.setAttribute('position_low', new Float32BufferAttribute(position_low, 3)) + geometry.setAttribute('position_high', new Float32BufferAttribute(position_high, 3)) + + return geometry } static makeLineGeometry_TRIANGLE(geometryData: GeometryData) { const geometry = new LineGeometry() geometry.setPositions(geometryData.attributes.POSITION) if (geometryData.attributes.COLOR) geometry.setColors(geometryData.attributes.COLOR) + geometry.computeBoundingBox() + const position_low = new Float32Array(geometryData.attributes.POSITION.length) + const position_high = new Float32Array(geometryData.attributes.POSITION.length) + for (var k = 0; k < geometryData.attributes.POSITION.length; k++) { + const doubleValue = geometryData.attributes.POSITION[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 + } + } + geometry.setAttribute('position_low', new Float32BufferAttribute(position_low, 3)) + geometry.setAttribute('position_high', new Float32BufferAttribute(position_high, 3)) return geometry } @@ -215,4 +279,37 @@ export class Geometry { } return colors } + + public static DoubleToHighLow(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 + } + } } diff --git a/packages/viewer/src/modules/materials/SpeckleLineMaterial.ts b/packages/viewer/src/modules/materials/SpeckleLineMaterial.ts new file mode 100644 index 000000000..5039b0e12 --- /dev/null +++ b/packages/viewer/src/modules/materials/SpeckleLineMaterial.ts @@ -0,0 +1,82 @@ +import { speckle_line_vert } from './shaders/speckle-line-vert' +import { speckle_line_frag } from './shaders/speckle-line-frag' +import { UniformsUtils, ShaderLib, Vector3, MeshStandardMaterial } 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() + + 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_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 + } + } + ]) + + 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 (var 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() + } + + return this + } + + onBeforeRender(_this, scene, camera, geometry, object, group) { + 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) + + let uViewer_low = new Vector3() + let uViewer_high = new Vector3() + let uViewer = new Vector3( + camera.matrixWorld.elements[12], + camera.matrixWorld.elements[13], + camera.matrixWorld.elements[14] + ) + + Geometry.DoubleToHighLow(uViewer, uViewer_low, uViewer_high) + this.userData.uViewer_high.value.copy(uViewer_high) + this.userData.uViewer_low.value.copy(uViewer_low) + this.needsUpdate = true + } +} + +export default SpeckleLineMaterial diff --git a/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts b/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts new file mode 100644 index 000000000..c0908d099 --- /dev/null +++ b/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts @@ -0,0 +1,81 @@ +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() + 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 + } + + for (var 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() + } + + return this + } + + onBeforeRender(_this, scene, camera, geometry, object, group) { + 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) + + let uViewer_low = new Vector3() + let uViewer_high = new Vector3() + let uViewer = new Vector3( + camera.matrixWorld.elements[12], + camera.matrixWorld.elements[13], + camera.matrixWorld.elements[14] + ) + + Geometry.DoubleToHighLow(uViewer, uViewer_low, uViewer_high) + object.frustumCulled = false + this.userData.uViewer_high.value.copy(uViewer_high) + this.userData.uViewer_low.value.copy(uViewer_low) + this.needsUpdate = true + } +} + +export default SpeckleStandardMaterial diff --git a/packages/viewer/src/modules/materials/shaders/speckle-line-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-line-frag.ts new file mode 100644 index 000000000..9e7c2bfd6 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-line-frag.ts @@ -0,0 +1,160 @@ +export const speckle_line_frag = /* glsl */ ` + uniform vec3 diffuse; + uniform float opacity; + uniform float linewidth; + + #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; + + #ifdef USE_DASH + + varying vec2 vUv; + + #endif + + #else + + varying vec2 vUv; + + #endif + + #include + #include + #include + #include + #include + + 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 + + #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 / linewidth; + + #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 + #include + + gl_FragColor = vec4( diffuseColor.rgb, alpha ); + + #include + #include + #include + #include + + } + ` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-line-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-line-vert.ts new file mode 100644 index 000000000..18732d90a --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-line-vert.ts @@ -0,0 +1,244 @@ +export const speckle_line_vert = /* glsl */ ` + #include + #include + #include + #include + #include + + uniform float linewidth; + uniform vec2 resolution; + + uniform vec3 uViewer_high; + uniform vec3 uViewer_low; + + attribute vec3 instanceStart; + attribute vec3 instanceEnd; + + attribute vec3 instanceColorStart; + attribute vec3 instanceColorEnd; + + attribute vec3 position_high; + attribute vec3 position_low; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #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 + + 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 ); + + } + + void main() { + vec3 highDifference = vec3(position_high.xyz - uViewer_high); + vec3 lowDifference = vec3(position_low.xyz - uViewer_low); + vec3 computedPosition = position;//highDifference.xyz + lowDifference.xyz; + #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 + vec3 startHighDifference = vec3(instanceStart.xyz - uViewer_high); + vec3 startLowDifference = vec3(instanceStart.xyz - uViewer_low); + vec3 endHighDifference = vec3(instanceEnd.xyz - uViewer_high); + vec3 endLowDifference = vec3(instanceEnd.xyz - uViewer_low); + vec4 start = modelViewMatrix * vec4( startLowDifference + startHighDifference, 1.0 ); + vec4 end = modelViewMatrix * vec4( endLowDifference + endHighDifference, 1.0 ); + + // vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); + // vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); + + #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; + + } + + // adjust for linewidth + offset *= linewidth * 0.5; + + // set the world position + worldPos = ( computedPosition.y < 0.5 ) ? start : end; + worldPos.xyz += offset; + + // 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 + #include + #include + + } + ` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-standard-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-standard-frag.ts new file mode 100644 index 000000000..d488eb70c --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-standard-frag.ts @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void main() { + + #include + + vec4 diffuseColor = vec4( diffuse, opacity ); + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + vec3 totalEmissiveRadiance = emissive; + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + // accumulation + #include + #include + #include + #include + + // modulation + #include + + vec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse; + vec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular; + + #include + + 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 + #include + #include + #include + #include + #include + +} +` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-standard-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-standard-vert.ts new file mode 100644 index 000000000..38bd7c4b7 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-standard-vert.ts @@ -0,0 +1,77 @@ +export const speckle_standard_vert = /* glsl */ ` +#define STANDARD +attribute vec3 position_high; +attribute vec3 position_low; + +varying vec3 vViewPosition; + +#ifdef USE_TRANSMISSION + + varying vec3 vWorldPosition; + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +uniform vec3 uViewer_high; +uniform vec3 uViewer_low; + +void main() { + + #include + #include + #include + + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + //#include // EDITED CHUNK + vec3 highDifference = vec3(position_high.xyz - uViewer_high); + vec3 lowDifference = vec3(position_low.xyz - uViewer_low); + vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.);//vec4( transformed - uViewer, 1.0 ); + + #ifdef USE_INSTANCING + + mvPosition = instanceMatrix * mvPosition; + + #endif + mvPosition = modelViewMatrix * mvPosition; + + gl_Position = projectionMatrix * mvPosition; + + + #include + #include + + vViewPosition = - mvPosition.xyz; + + #include + #include + #include + +#ifdef USE_TRANSMISSION + + vWorldPosition = worldPosition.xyz; + +#endif +} +`