WIP on headless-gl rendering
This commit is contained in:
Родитель
3142412509
Коммит
e04510577f
|
@ -58,6 +58,7 @@
|
|||
"@speckle/objectloader": "workspace:^",
|
||||
"@speckle/shared": "workspace:^",
|
||||
"@types/flat": "^5.0.2",
|
||||
"canvas": "^2.11.2",
|
||||
"flat": "^5.0.2",
|
||||
"js-logger": "1.6.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
|
|
@ -34,15 +34,15 @@ export interface ViewerParams {
|
|||
}
|
||||
export enum AssetType {
|
||||
TEXTURE_8BPP = 'png', // For now
|
||||
TEXTURE_HDR = 'hdr',
|
||||
TEXTURE_EXR = 'exr',
|
||||
FONT_JSON = 'font-json'
|
||||
}
|
||||
|
||||
export interface Asset {
|
||||
id: string
|
||||
src: string
|
||||
type: AssetType
|
||||
src?: string
|
||||
contentsBuffer?: ArrayBuffer
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,32 +6,31 @@ import {
|
|||
DataTexture,
|
||||
DataTextureLoader,
|
||||
Matrix4,
|
||||
Euler
|
||||
Euler,
|
||||
ClampToEdgeWrapping,
|
||||
LinearFilter,
|
||||
FloatType
|
||||
} from 'three'
|
||||
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js'
|
||||
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
|
||||
import { FontLoader, Font } from 'three/examples/jsm/loaders/FontLoader.js'
|
||||
import { type Asset, AssetType } from '../IViewer'
|
||||
import Logger from 'js-logger'
|
||||
import { RotatablePMREMGenerator } from './objects/RotatablePMREMGenerator'
|
||||
import { Image as CanvasImage, createCanvas } from 'canvas'
|
||||
|
||||
export class Assets {
|
||||
private static _cache: { [name: string]: Texture | Font } = {}
|
||||
public static _cache: { [name: string]: Texture | Font } = {}
|
||||
|
||||
private static getLoader(
|
||||
src: string,
|
||||
assetType: AssetType
|
||||
): TextureLoader | DataTextureLoader | null {
|
||||
if (assetType === undefined) assetType = src.split('.').pop() as AssetType
|
||||
if (!Object.values(AssetType).includes(assetType)) {
|
||||
Logger.warn(`Asset ${src} could not be loaded. Unknown type`)
|
||||
Logger.warn(`Asset ${assetType} 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()
|
||||
default:
|
||||
|
@ -61,19 +60,40 @@ export class Assets {
|
|||
}
|
||||
|
||||
return new Promise<Texture>((resolve, reject) => {
|
||||
const loader = Assets.getLoader(asset.src, asset.type)
|
||||
const loader = Assets.getLoader(asset.type)
|
||||
if (loader) {
|
||||
loader.load(
|
||||
asset.src,
|
||||
(texture) => {
|
||||
this._cache[asset.id] = texture
|
||||
resolve(Assets.hdriToPMREM(renderer, texture))
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
reject(`Loading asset ${asset.id} failed ${error.message}`)
|
||||
}
|
||||
)
|
||||
if (asset.src) {
|
||||
loader.load(
|
||||
asset.src,
|
||||
(texture) => {
|
||||
this._cache[asset.id] = texture
|
||||
resolve(Assets.hdriToPMREM(renderer, texture))
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
reject(`Loading asset ${asset.id} failed ${error.message}`)
|
||||
}
|
||||
)
|
||||
} else if (asset.contentsBuffer) {
|
||||
const texData = (loader as EXRLoader).parse(asset.contentsBuffer)
|
||||
if (!texData) reject(`Loading asset ${asset.id} failed`)
|
||||
const texture = new DataTexture(
|
||||
texData.data,
|
||||
texData.width,
|
||||
texData.height,
|
||||
texData.format,
|
||||
FloatType,
|
||||
undefined,
|
||||
ClampToEdgeWrapping,
|
||||
ClampToEdgeWrapping,
|
||||
LinearFilter,
|
||||
LinearFilter,
|
||||
1
|
||||
)
|
||||
texture.needsUpdate = true
|
||||
this._cache[asset.id] = texture
|
||||
resolve(Assets.hdriToPMREM(renderer, texture))
|
||||
}
|
||||
} else {
|
||||
reject(`Loading asset ${asset.id} failed`)
|
||||
}
|
||||
|
@ -85,64 +105,80 @@ export class Assets {
|
|||
return Promise.resolve(this._cache[asset.id] as Texture)
|
||||
}
|
||||
return new Promise<Texture>((resolve, reject) => {
|
||||
// Hack to load 'data:image's - for some reason, the frontend receives the default
|
||||
// gradient map as a data image url, rather than a file (?).
|
||||
if (asset.src.includes('data:image')) {
|
||||
const image = new Image()
|
||||
image.src = asset.src
|
||||
if (asset.src) {
|
||||
if (asset.src.includes('data:image')) {
|
||||
const image = new CanvasImage()
|
||||
image.src = asset.src
|
||||
image.onload = () => {
|
||||
const canvas = createCanvas(image.width, image.height)
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(image, 0, 0)
|
||||
const texture = new Texture(new Image())
|
||||
texture.needsUpdate = true
|
||||
this._cache[asset.id] = texture
|
||||
resolve(texture)
|
||||
}
|
||||
image.onerror = (ev) => {
|
||||
reject(`Loading asset ${asset.id} failed with ${ev.toString()}`)
|
||||
}
|
||||
} else {
|
||||
const loader = Assets.getLoader(asset.type)
|
||||
if (loader) {
|
||||
if (asset.src) {
|
||||
loader.load(
|
||||
asset.src,
|
||||
(texture) => {
|
||||
this._cache[asset.id] = texture
|
||||
resolve(this._cache[asset.id] as Texture)
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
reject(`Loading asset ${asset.id} failed ${error.message}`)
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
reject(`Loading asset ${asset.id} failed`)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (asset.contentsBuffer) {
|
||||
const image = new CanvasImage()
|
||||
image.onload = () => {
|
||||
const texture = new Texture(image)
|
||||
const canvas = createCanvas(image.width, image.height)
|
||||
const ctx = canvas.getContext('2d')
|
||||
ctx.drawImage(image, 0, 0)
|
||||
const texture = new Texture(new Image())
|
||||
texture.needsUpdate = true
|
||||
this._cache[asset.id] = texture
|
||||
resolve(texture)
|
||||
}
|
||||
image.onerror = (ev) => {
|
||||
reject(`Loading asset ${asset.id} failed with ${ev.toString()}`)
|
||||
}
|
||||
} else {
|
||||
const loader = Assets.getLoader(asset.src, asset.type)
|
||||
if (loader) {
|
||||
loader.load(
|
||||
asset.src,
|
||||
(texture) => {
|
||||
this._cache[asset.id] = texture
|
||||
resolve(this._cache[asset.id] as Texture)
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
reject(`Loading asset ${asset.id} failed ${error.message}`)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
reject(`Loading asset ${asset.id} failed`)
|
||||
image.onerror = (err) => {
|
||||
throw err
|
||||
}
|
||||
image.src = Buffer.from(asset.contentsBuffer)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public static getFont(asset: Asset | string): Promise<Font> {
|
||||
let srcUrl: string | null = null
|
||||
if ((<Asset>asset).src) {
|
||||
srcUrl = (asset as Asset).src
|
||||
} else {
|
||||
srcUrl = asset as string
|
||||
}
|
||||
|
||||
if (this._cache[srcUrl]) {
|
||||
return Promise.resolve(this._cache[srcUrl] as Font)
|
||||
public static getFont(asset: Asset): Promise<Font> {
|
||||
if (this._cache[asset.id]) {
|
||||
return Promise.resolve(this._cache[asset.id] as Font)
|
||||
}
|
||||
|
||||
return new Promise<Font>((resolve, reject) => {
|
||||
new FontLoader().load(
|
||||
srcUrl as string,
|
||||
(font: Font) => {
|
||||
resolve(font)
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
reject(`Loading asset ${srcUrl} failed ${error.message}`)
|
||||
}
|
||||
)
|
||||
if (asset.src) {
|
||||
new FontLoader().load(
|
||||
asset.src,
|
||||
(font: Font) => {
|
||||
resolve(font)
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
reject(`Loading asset ${asset.id} failed ${error.message}`)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -78,13 +78,17 @@ export class RenderingStats {
|
|||
public batchCount: number = 0
|
||||
public drawCalls: number = 0
|
||||
public trisCount: number = 0
|
||||
public lineCount: number = 0
|
||||
public pointCount: number = 0
|
||||
public vertCount: number = 0
|
||||
|
||||
public batchDetails!: Array<{
|
||||
drawCalls: number
|
||||
minDrawCalls: number
|
||||
tris: number
|
||||
verts: number
|
||||
tris?: number
|
||||
lines?: number
|
||||
points?: number
|
||||
}>
|
||||
|
||||
public frameStart() {
|
||||
|
@ -133,6 +137,7 @@ export default class SpeckleRenderer {
|
|||
public viewer: Viewer // TEMPORARY
|
||||
public pipeline: Pipeline
|
||||
public input: Input
|
||||
public materials: Materials
|
||||
|
||||
/********************************
|
||||
* Renderer and rendering flags */
|
||||
|
@ -295,14 +300,24 @@ export default class SpeckleRenderer {
|
|||
(a: number, c: Batch) => a + c.vertCount,
|
||||
0
|
||||
)),
|
||||
(this._renderinStats.pointCount = batches.reduce(
|
||||
(a: number, c: Batch) => a + c.pointCount,
|
||||
0
|
||||
)),
|
||||
(this._renderinStats.lineCount = batches.reduce(
|
||||
(a: number, c: Batch) => a + c.lineCount,
|
||||
0
|
||||
)),
|
||||
(this._renderinStats.batchDetails = batches.map((batch: Batch) => {
|
||||
return {
|
||||
type: batch.constructor.name,
|
||||
objCount: batch.renderViews.length,
|
||||
drawCalls: batch.drawCalls,
|
||||
minDrawCalls: batch.minDrawCalls,
|
||||
tris: batch.triCount,
|
||||
verts: batch.vertCount
|
||||
verts: batch.vertCount,
|
||||
...(batch.triCount && { tris: batch.triCount }),
|
||||
...(batch.lineCount && { lines: batch.lineCount }),
|
||||
...(batch.pointCount && { points: batch.pointCount })
|
||||
}
|
||||
}))
|
||||
return this._renderinStats
|
||||
|
@ -345,7 +360,9 @@ export default class SpeckleRenderer {
|
|||
this._renderer.setSize(container.offsetWidth, container.offsetHeight)
|
||||
container.appendChild(this._renderer.domElement)
|
||||
|
||||
this.materials = new Materials()
|
||||
this.batcher = new Batcher(
|
||||
this.materials,
|
||||
this.renderer.capabilities.maxVertexUniforms,
|
||||
this.renderer.capabilities.floatVertexTextures
|
||||
)
|
||||
|
@ -404,6 +421,10 @@ export default class SpeckleRenderer {
|
|||
this._scene.add(this._shadowcatcher.shadowcatcherMesh)
|
||||
}
|
||||
|
||||
public async init() {
|
||||
await this.materials.createDefaultMaterials()
|
||||
}
|
||||
|
||||
public update(deltaTime: number) {
|
||||
if (!this.renderingCamera) return
|
||||
this.batcher.update(deltaTime)
|
||||
|
@ -709,11 +730,8 @@ export default class SpeckleRenderer {
|
|||
return {
|
||||
offset: value.batchStart,
|
||||
count: value.batchCount,
|
||||
material: this.batcher.materials.getFilterMaterial(
|
||||
value,
|
||||
material
|
||||
) as Material,
|
||||
materialOptions: this.batcher.materials.getFilterMaterialOptions(
|
||||
material: this.materials.getFilterMaterial(value, material) as Material,
|
||||
materialOptions: this.materials.getFilterMaterialOptions(
|
||||
material
|
||||
) as FilterMaterialOptions
|
||||
}
|
||||
|
@ -729,7 +747,7 @@ export default class SpeckleRenderer {
|
|||
) {
|
||||
for (const k in rvs) {
|
||||
const drawRanges = rvs[k].map((value: NodeRenderView) => {
|
||||
const material = this.batcher.materials.getDataMaterial(value, materialData)
|
||||
const material = this.materials.getDataMaterial(value, materialData)
|
||||
;(material as unknown as SpeckleMaterial).setMaterialOptions(materialData)
|
||||
return {
|
||||
offset: value.batchStart,
|
||||
|
|
|
@ -240,6 +240,7 @@ export class Viewer extends EventEmitter implements IViewer {
|
|||
Logger.error('Fallback to null environment!')
|
||||
})
|
||||
}
|
||||
await this.speckleRenderer.init()
|
||||
}
|
||||
|
||||
on<T extends ViewerEvent>(
|
||||
|
|
|
@ -39,17 +39,20 @@ export default class Batcher {
|
|||
private maxBatchObjects = 0
|
||||
private maxBatchVertices = 500000
|
||||
private minInstancedBatchVertices = 10000
|
||||
public materials: Materials
|
||||
private materials: Materials
|
||||
public batches: { [id: string]: Batch } = {}
|
||||
|
||||
public constructor(maxUniformCount: number, floatTextures: boolean) {
|
||||
public constructor(
|
||||
materials: Materials,
|
||||
maxUniformCount: number,
|
||||
floatTextures: boolean
|
||||
) {
|
||||
this.maxHardwareUniformCount = maxUniformCount
|
||||
this.maxBatchObjects = Math.floor(
|
||||
(this.maxHardwareUniformCount - Materials.UNIFORM_VECTORS_USED) / 4
|
||||
)
|
||||
this.floatTextures = floatTextures
|
||||
this.materials = new Materials()
|
||||
void this.materials.createDefaultMaterials()
|
||||
this.materials = materials
|
||||
}
|
||||
|
||||
public async *makeBatches(
|
||||
|
|
|
@ -49,11 +49,20 @@ export default class LineBatch implements Batch {
|
|||
}
|
||||
|
||||
public get triCount(): number {
|
||||
return 0
|
||||
const indexCount = this.geometry.index ? this.geometry.index.count : 0
|
||||
const buffer = (
|
||||
this.geometry.attributes.instanceStart as InterleavedBufferAttribute
|
||||
).data as InstancedInterleavedBuffer
|
||||
return (indexCount / 3) * buffer.meshPerAttribute * buffer.count
|
||||
}
|
||||
|
||||
public get vertCount(): number {
|
||||
return this.geometry.attributes.position.count * this.geometry.instanceCount
|
||||
const buffer = (
|
||||
this.geometry.attributes.instanceStart as InterleavedBufferAttribute
|
||||
).data as InstancedInterleavedBuffer
|
||||
return (
|
||||
this.geometry.attributes.position.count * buffer.meshPerAttribute * buffer.count
|
||||
)
|
||||
}
|
||||
|
||||
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
|
||||
|
@ -61,15 +70,19 @@ export default class LineBatch implements Batch {
|
|||
this.subtreeId = subtreeId
|
||||
this.renderViews = renderViews
|
||||
}
|
||||
|
||||
public get pointCount(): number {
|
||||
return 0
|
||||
}
|
||||
|
||||
public get lineCount(): number {
|
||||
/** Catering to typescript
|
||||
* There is no unniverse where the geometry is non-indexed. LineSegments2 are **explicitly** indexed
|
||||
*/
|
||||
const indexCount = this.geometry.index ? this.geometry.index.count : 0
|
||||
return (indexCount / 3) * (this.geometry as never)['_maxInstanceCount']
|
||||
const buffer = (
|
||||
this.geometry.attributes.instanceStart as InterleavedBufferAttribute
|
||||
).data as InstancedInterleavedBuffer
|
||||
return buffer.meshPerAttribute * buffer.count
|
||||
}
|
||||
|
||||
public get renderObject(): Object3D {
|
||||
|
|
|
@ -1,16 +1,327 @@
|
|||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
import {
|
||||
BufferAttribute,
|
||||
BufferGeometry,
|
||||
CubeUVReflectionMapping,
|
||||
FloatType,
|
||||
LinearEncoding,
|
||||
LinearFilter,
|
||||
Matrix4,
|
||||
NoBlending,
|
||||
PMREMGenerator,
|
||||
RGBAFormat,
|
||||
ShaderMaterial,
|
||||
WebGLRenderer
|
||||
Vector3,
|
||||
WebGLRenderer,
|
||||
WebGLRenderTarget,
|
||||
WebGLRenderTargetOptions
|
||||
} from 'three'
|
||||
|
||||
const MAX_SAMPLES = 20
|
||||
const LOD_MIN = 4
|
||||
const EXTRA_LOD_SIGMA = [0.125, 0.215, 0.35, 0.446, 0.526, 0.582]
|
||||
|
||||
export class RotatablePMREMGenerator extends PMREMGenerator {
|
||||
constructor(renderer: WebGLRenderer) {
|
||||
super(renderer)
|
||||
}
|
||||
|
||||
_allocateTargets() {
|
||||
//@ts-ignore
|
||||
const width = 3 * Math.max(this._cubeSize, 16 * 7)
|
||||
//@ts-ignore
|
||||
const height = 4 * this._cubeSize
|
||||
|
||||
const params = {
|
||||
magFilter: LinearFilter,
|
||||
minFilter: LinearFilter,
|
||||
generateMipmaps: false,
|
||||
type: FloatType,
|
||||
format: RGBAFormat,
|
||||
encoding: LinearEncoding,
|
||||
depthBuffer: false
|
||||
}
|
||||
//@ts-ignore
|
||||
const cubeUVRenderTarget = this._createRenderTarget(width, height, params)
|
||||
|
||||
if (
|
||||
//@ts-ignore
|
||||
this._pingPongRenderTarget === null ||
|
||||
//@ts-ignore
|
||||
this._pingPongRenderTarget.width !== width
|
||||
) {
|
||||
//@ts-ignore
|
||||
if (this._pingPongRenderTarget !== null) {
|
||||
//@ts-ignore
|
||||
this._dispose()
|
||||
}
|
||||
//@ts-ignore
|
||||
this._pingPongRenderTarget = this._createRenderTarget(width, height, params)
|
||||
//@ts-ignore
|
||||
const { _lodMax } = this
|
||||
;({
|
||||
//@ts-ignore
|
||||
sizeLods: this._sizeLods,
|
||||
//@ts-ignore
|
||||
lodPlanes: this._lodPlanes,
|
||||
//@ts-ignore
|
||||
sigmas: this._sigmas
|
||||
//@ts-ignore
|
||||
} = this._createPlanes(_lodMax))
|
||||
//@ts-ignore
|
||||
this._blurMaterial = this._getBlurShader(_lodMax, width, height)
|
||||
}
|
||||
|
||||
return cubeUVRenderTarget
|
||||
}
|
||||
|
||||
public _createRenderTarget(
|
||||
width: number,
|
||||
height: number,
|
||||
params: WebGLRenderTargetOptions
|
||||
) {
|
||||
const cubeUVRenderTarget = new WebGLRenderTarget(width, height, params)
|
||||
cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping
|
||||
cubeUVRenderTarget.texture.name = 'PMREM.cubeUv'
|
||||
cubeUVRenderTarget.scissorTest = true
|
||||
return cubeUVRenderTarget
|
||||
}
|
||||
|
||||
public _getBlurShader(lodMax: number, width: number, height: number) {
|
||||
const weights = new Float32Array(MAX_SAMPLES)
|
||||
const poleAxis = new Vector3(0, 1, 0)
|
||||
const shaderMaterial = new ShaderMaterial({
|
||||
name: 'SphericalGaussianBlur',
|
||||
|
||||
defines: {
|
||||
n: MAX_SAMPLES,
|
||||
CUBEUV_TEXEL_WIDTH: 1.0 / width,
|
||||
CUBEUV_TEXEL_HEIGHT: 1.0 / height,
|
||||
CUBEUV_MAX_MIP: `${lodMax}.0`
|
||||
},
|
||||
|
||||
uniforms: {
|
||||
envMap: { value: null },
|
||||
samples: { value: 1 },
|
||||
weights: { value: weights },
|
||||
latitudinal: { value: false },
|
||||
dTheta: { value: 0 },
|
||||
mipInt: { value: 0 },
|
||||
poleAxis: { value: poleAxis }
|
||||
},
|
||||
|
||||
vertexShader: this._getCommonVertexShader(),
|
||||
|
||||
fragmentShader: /* glsl */ `
|
||||
|
||||
precision mediump float;
|
||||
precision mediump int;
|
||||
|
||||
varying vec3 vOutputDirection;
|
||||
|
||||
uniform sampler2D envMap;
|
||||
uniform int samples;
|
||||
uniform float weights[ n ];
|
||||
uniform bool latitudinal;
|
||||
uniform float dTheta;
|
||||
uniform float mipInt;
|
||||
uniform vec3 poleAxis;
|
||||
|
||||
#define ENVMAP_TYPE_CUBE_UV
|
||||
#include <cube_uv_reflection_fragment>
|
||||
|
||||
vec3 getSample( float theta, vec3 axis ) {
|
||||
|
||||
float cosTheta = cos( theta );
|
||||
// Rodrigues' axis-angle rotation
|
||||
vec3 sampleDirection = vOutputDirection * cosTheta
|
||||
+ cross( axis, vOutputDirection ) * sin( theta )
|
||||
+ axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );
|
||||
|
||||
return bilinearCubeUV( envMap, sampleDirection, mipInt );
|
||||
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );
|
||||
|
||||
if ( all( equal( axis, vec3( 0.0 ) ) ) ) {
|
||||
|
||||
axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );
|
||||
|
||||
}
|
||||
|
||||
axis = normalize( axis );
|
||||
|
||||
gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
|
||||
gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );
|
||||
|
||||
for ( int i = 1; i < n; i++ ) {
|
||||
|
||||
if ( i >= samples ) {
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
float theta = dTheta * float( i );
|
||||
gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );
|
||||
gl_FragColor.rgb += weights[ i ] * getSample( theta, axis );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
`,
|
||||
|
||||
blending: NoBlending,
|
||||
depthTest: false,
|
||||
depthWrite: false
|
||||
})
|
||||
|
||||
return shaderMaterial
|
||||
}
|
||||
|
||||
public _getCommonVertexShader() {
|
||||
return /* glsl */ `
|
||||
|
||||
precision mediump float;
|
||||
precision mediump int;
|
||||
|
||||
attribute float faceIndex;
|
||||
|
||||
varying vec3 vOutputDirection;
|
||||
|
||||
// RH coordinate system; PMREM face-indexing convention
|
||||
vec3 getDirection( vec2 uv, float face ) {
|
||||
|
||||
uv = 2.0 * uv - 1.0;
|
||||
|
||||
vec3 direction = vec3( uv, 1.0 );
|
||||
|
||||
if ( face == 0.0 ) {
|
||||
|
||||
direction = direction.zyx; // ( 1, v, u ) pos x
|
||||
|
||||
} else if ( face == 1.0 ) {
|
||||
|
||||
direction = direction.xzy;
|
||||
direction.xz *= -1.0; // ( -u, 1, -v ) pos y
|
||||
|
||||
} else if ( face == 2.0 ) {
|
||||
|
||||
direction.x *= -1.0; // ( -u, v, 1 ) pos z
|
||||
|
||||
} else if ( face == 3.0 ) {
|
||||
|
||||
direction = direction.zyx;
|
||||
direction.xz *= -1.0; // ( -1, v, -u ) neg x
|
||||
|
||||
} else if ( face == 4.0 ) {
|
||||
|
||||
direction = direction.xzy;
|
||||
direction.xy *= -1.0; // ( -u, -1, v ) neg y
|
||||
|
||||
} else if ( face == 5.0 ) {
|
||||
|
||||
direction.z *= -1.0; // ( u, v, -1 ) neg z
|
||||
|
||||
}
|
||||
|
||||
return direction;
|
||||
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
vOutputDirection = getDirection( uv, faceIndex );
|
||||
gl_Position = vec4( position, 1.0 );
|
||||
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
public _createPlanes(lodMax: number) {
|
||||
const lodPlanes = []
|
||||
const sizeLods = []
|
||||
const sigmas = []
|
||||
|
||||
let lod = lodMax
|
||||
|
||||
const totalLods = lodMax - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length
|
||||
|
||||
for (let i = 0; i < totalLods; i++) {
|
||||
const sizeLod = Math.pow(2, lod)
|
||||
sizeLods.push(sizeLod)
|
||||
let sigma = 1.0 / sizeLod
|
||||
|
||||
if (i > lodMax - LOD_MIN) {
|
||||
sigma = EXTRA_LOD_SIGMA[i - lodMax + LOD_MIN - 1]
|
||||
} else if (i === 0) {
|
||||
sigma = 0
|
||||
}
|
||||
|
||||
sigmas.push(sigma)
|
||||
|
||||
const texelSize = 1.0 / (sizeLod - 2)
|
||||
const min = -texelSize
|
||||
const max = 1 + texelSize
|
||||
const uv1 = [min, min, max, min, max, max, min, min, max, max, min, max]
|
||||
|
||||
const cubeFaces = 6
|
||||
const vertices = 6
|
||||
const positionSize = 3
|
||||
const uvSize = 2
|
||||
const faceIndexSize = 1
|
||||
|
||||
const position = new Float32Array(positionSize * vertices * cubeFaces)
|
||||
const uv = new Float32Array(uvSize * vertices * cubeFaces)
|
||||
const faceIndex = new Float32Array(faceIndexSize * vertices * cubeFaces)
|
||||
|
||||
for (let face = 0; face < cubeFaces; face++) {
|
||||
const x = ((face % 3) * 2) / 3 - 1
|
||||
const y = face > 2 ? 0 : -1
|
||||
const coordinates = [
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
x + 2 / 3,
|
||||
y,
|
||||
0,
|
||||
x + 2 / 3,
|
||||
y + 1,
|
||||
0,
|
||||
x,
|
||||
y,
|
||||
0,
|
||||
x + 2 / 3,
|
||||
y + 1,
|
||||
0,
|
||||
x,
|
||||
y + 1,
|
||||
0
|
||||
]
|
||||
position.set(coordinates, positionSize * vertices * face)
|
||||
uv.set(uv1, uvSize * vertices * face)
|
||||
const fill = [face, face, face, face, face, face]
|
||||
faceIndex.set(fill, faceIndexSize * vertices * face)
|
||||
}
|
||||
|
||||
const planes = new BufferGeometry()
|
||||
planes.setAttribute('position', new BufferAttribute(position, positionSize))
|
||||
planes.setAttribute('uv', new BufferAttribute(uv, uvSize))
|
||||
planes.setAttribute('faceIndex', new BufferAttribute(faceIndex, faceIndexSize))
|
||||
lodPlanes.push(planes)
|
||||
|
||||
if (lod > LOD_MIN) {
|
||||
lod--
|
||||
}
|
||||
}
|
||||
|
||||
return { lodPlanes, sizeLods, sigmas }
|
||||
}
|
||||
|
||||
public compileProperEquirectShader(rotationMatrix?: Matrix4) {
|
||||
const fixedEnvFlip = function _getEquirectMaterial() {
|
||||
return new ShaderMaterial({
|
||||
|
@ -76,10 +387,9 @@ export class RotatablePMREMGenerator extends PMREMGenerator {
|
|||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
this._equirectMaterial = fixedEnvFlip()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
|
||||
// @ts-ignore
|
||||
this._compileMaterial(this._equirectMaterial)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import { test } from 'vitest'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import { Viewer } from '../src'
|
||||
import {
|
||||
Assets,
|
||||
AssetType,
|
||||
CameraController,
|
||||
SpeckleLoader,
|
||||
UrlHelper,
|
||||
Viewer,
|
||||
ViewerEvent
|
||||
} from '../src'
|
||||
import { readFile } from 'fs/promises'
|
||||
|
||||
test('Viewer', async () => {
|
||||
test('Viewer', { timeout: 20000 }, async () => {
|
||||
const width = 64
|
||||
const height = 64
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
|
@ -10,5 +19,58 @@ test('Viewer', async () => {
|
|||
gl
|
||||
const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`)
|
||||
const viewer = new Viewer(dom.window.document.createElement('div'), undefined, gl)
|
||||
console.warn(viewer)
|
||||
// console.log(viewer.getRenderer().renderer.extensions)
|
||||
const hdriContents = await readFile('src/assets/hdri/Mild-dwab.png')
|
||||
const gradientContents = await readFile('src/assets/gradient.png')
|
||||
await Assets.getEnvironment(
|
||||
{
|
||||
id: 'defaultHDRI',
|
||||
type: AssetType.TEXTURE_EXR,
|
||||
contentsBuffer: hdriContents.buffer
|
||||
},
|
||||
viewer.getRenderer().renderer
|
||||
)
|
||||
await Assets.getTexture({
|
||||
id: 'defaultGradient',
|
||||
type: AssetType.TEXTURE_8BPP,
|
||||
contentsBuffer: gradientContents.buffer
|
||||
})
|
||||
// Assets._cache['defaultHDRI'] = new EXRLoader().parse(buffer)
|
||||
// Assets._cache['defaultGradient'] = new DataTexture(null, 512, 1)
|
||||
try {
|
||||
await viewer.init()
|
||||
} catch (e) {}
|
||||
viewer.createExtension(CameraController)
|
||||
|
||||
viewer.on(ViewerEvent.LoadComplete, () => {
|
||||
viewer.getRenderer().render()
|
||||
setTimeout(() => {
|
||||
const pixels = new Uint8Array(width * height * 4)
|
||||
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
|
||||
process.stdout.write(['P3\n# gl.ppm\n', width, ' ', height, '\n255\n'].join(''))
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
for (let j = 0; j < 3; ++j) {
|
||||
process.stdout.write(pixels[i + j] + ' ')
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
const url = 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8'
|
||||
const authToken = localStorage.getItem(
|
||||
url.includes('latest') ? 'AuthTokenLatest' : 'AuthToken'
|
||||
) as string
|
||||
const objUrls = await UrlHelper.getResourceUrls(url, authToken)
|
||||
for (const objURL of objUrls) {
|
||||
// console.log(`Loading ${url}`)
|
||||
const loader = new SpeckleLoader(
|
||||
viewer.getWorldTree(),
|
||||
objURL,
|
||||
authToken,
|
||||
true,
|
||||
undefined
|
||||
)
|
||||
await viewer.loadObject(loader, false)
|
||||
}
|
||||
// console.warn(viewer.getRenderer().renderingStats)
|
||||
})
|
||||
|
|
44
yarn.lock
44
yarn.lock
|
@ -11748,7 +11748,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@mapbox/node-pre-gyp@npm:^1.0.11":
|
||||
"@mapbox/node-pre-gyp@npm:^1.0.0, @mapbox/node-pre-gyp@npm:^1.0.11":
|
||||
version: 1.0.11
|
||||
resolution: "@mapbox/node-pre-gyp@npm:1.0.11"
|
||||
dependencies:
|
||||
|
@ -16026,6 +16026,7 @@ __metadata:
|
|||
"@typescript-eslint/eslint-plugin": "npm:^7.12.0"
|
||||
"@typescript-eslint/parser": "npm:^7.12.0"
|
||||
"@vitest/ui": "npm:^1.4.0"
|
||||
canvas: "npm:^2.11.2"
|
||||
core-js: "npm:^3.21.1"
|
||||
eslint: "npm:^9.4.0"
|
||||
eslint-config-prettier: "npm:^9.1.0"
|
||||
|
@ -24916,6 +24917,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"canvas@npm:^2.11.2":
|
||||
version: 2.11.2
|
||||
resolution: "canvas@npm:2.11.2"
|
||||
dependencies:
|
||||
"@mapbox/node-pre-gyp": "npm:^1.0.0"
|
||||
nan: "npm:^2.17.0"
|
||||
node-gyp: "npm:latest"
|
||||
simple-get: "npm:^3.0.3"
|
||||
checksum: 10/500040e93310b33f5733746b909712fdeced56aa74a1370c563f0c7ffc5b4a31006b2d881644b59eea8ab6c465406705a59b280f1d4e3ec4e1e2c88d4a725ca6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"capital-case@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "capital-case@npm:1.0.4"
|
||||
|
@ -27363,6 +27376,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress-response@npm:^4.2.0":
|
||||
version: 4.2.1
|
||||
resolution: "decompress-response@npm:4.2.1"
|
||||
dependencies:
|
||||
mimic-response: "npm:^2.0.0"
|
||||
checksum: 10/4e783ca4dfe9417354d61349750fe05236f565a4415a6ca20983a311be2371debaedd9104c0b0e7b36e5f167aeaae04f84f1a0b3f8be4162f1d7d15598b8fdba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decompress-response@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "decompress-response@npm:6.0.0"
|
||||
|
@ -37591,6 +37613,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-response@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "mimic-response@npm:2.1.0"
|
||||
checksum: 10/014fad6ab936657e5f2f48bd87af62a8e928ebe84472aaf9e14fec4fcb31257a5edff77324d8ac13ddc6685ba5135cf16e381efac324e5f174fb4ddbf902bf07
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-response@npm:^3.1.0":
|
||||
version: 3.1.0
|
||||
resolution: "mimic-response@npm:3.1.0"
|
||||
|
@ -39119,7 +39148,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nan@npm:^2.18.0, nan@npm:^2.19.0":
|
||||
"nan@npm:^2.17.0, nan@npm:^2.18.0, nan@npm:^2.19.0":
|
||||
version: 2.20.0
|
||||
resolution: "nan@npm:2.20.0"
|
||||
dependencies:
|
||||
|
@ -46484,6 +46513,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"simple-get@npm:^3.0.3":
|
||||
version: 3.1.1
|
||||
resolution: "simple-get@npm:3.1.1"
|
||||
dependencies:
|
||||
decompress-response: "npm:^4.2.0"
|
||||
once: "npm:^1.3.1"
|
||||
simple-concat: "npm:^1.0.0"
|
||||
checksum: 10/94fa04e74077c2607142f7597af8409c6c8d1e9487b597ce1da6f824e732b3e51ef492e495a4d8a2a12a94780214d77a8d3bb81c2139b3ec4ce21b93224442c0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"simple-get@npm:^4.0.0, simple-get@npm:^4.0.1":
|
||||
version: 4.0.1
|
||||
resolution: "simple-get@npm:4.0.1"
|
||||
|
|
Загрузка…
Ссылка в новой задаче