diff --git a/.eslintrc.json b/.eslintrc.json index 755108d..894bf47 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -64,6 +64,7 @@ "no-console": [0], "node/no-unsupported-features/es-builtins": 0, "node/no-unsupported-features/node-builtins": 0, + "node/no-unsupported-features/es-syntax": 0, "func-names": [ "error", "never", diff --git a/README.md b/README.md index d60c8c7..5cb5375 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is a part of [Node3D](https://github.com/node-3d) project. [![ESLint](https://github.com/node-3d/3d-core-raub/actions/workflows/eslint.yml/badge.svg)](https://github.com/node-3d/3d-core-raub/actions/workflows/eslint.yml) [![Test](https://github.com/node-3d/3d-core-raub/actions/workflows/test.yml/badge.svg)](https://github.com/node-3d/3d-core-raub/actions/workflows/test.yml) -``` +```console npm i -s 3d-core-raub ``` diff --git a/examples/palette/index.js b/examples/palette/index.js new file mode 100644 index 0000000..b9061c7 --- /dev/null +++ b/examples/palette/index.js @@ -0,0 +1,232 @@ + +const { read } = require('addon-tools-raub'); +const glfw = require('glfw-raub'); + +const { init, addThreeHelpers } = require('../../index'); +const { generatePalette } = require('./utils/palette'); +const { debugShaders } = require('./utils/debug-shaders'); +const { createPostMaterial } = require('./utils/create-post-material'); +const { createColorQuads } = require('./utils/create-color-quads'); +const { createRenderTarget } = require('./utils/create-render-target'); +const { populateScene } = require('./utils/populate-scene'); + +const hueModes = [ + 'monochromatic', 'analagous', 'complementary', 'triadic', 'tetradic', +]; + +const { Window } = glfw; + +(async () => { + const THREE = await import('three'); + const { OrbitControls } = await import('three/examples/jsm/controls/OrbitControls.js'); + + const fragmentShader = await read(`${__dirname}/post.glsl`); + + const { + doc, requestAnimationFrame, gl, + } = init({ isGles3: true, width: 1280, height: 720 }); + addThreeHelpers(THREE, gl); + + doc.setPointerCapture = () => { + doc.setInputMode(glfw.CURSOR, glfw.CURSOR_DISABLED); + }; + doc.releasePointerCapture = () => { + doc.setInputMode(glfw.CURSOR, glfw.CURSOR_NORMAL); + }; + doc.on('mousedown', (e) => { doc.emit('pointerdown', e); }); + doc.on('mouseup', (e) => { doc.emit('pointerup', e); }); + doc.on('mousemove', (e) => { doc.emit('pointermove', e); }); + + const cameraOrtho = new THREE.OrthographicCamera( + -doc.w / 2, doc.w / 2, doc.h / 2, -doc.h / 2, - 10, 10, + ); + cameraOrtho.position.z = 5; + + const cameraPerspective = new THREE.PerspectiveCamera(50, doc.w / doc.h, 1, 1000); + const controls = new OrbitControls(cameraPerspective, doc); + + cameraPerspective.position.z = 9; + controls.update(); + + const scene = new THREE.Scene(); + let mesh; + populateScene(scene, (m) => { mesh = m; }); + + const scenePost = new THREE.Scene(); + + let isSwap = false; + let modeGrayscale = 0; + let modeHue = 0; + let numColors = 9; + + const rawPalette0 = generatePalette(hueModes[modeHue], numColors); + let palette = rawPalette0.map((c) => (new THREE.Color(...c))); + + let materialPost = createPostMaterial(THREE, numColors, isSwap, modeGrayscale, palette, fragmentShader); + + let rt = createRenderTarget(THREE, materialPost, doc.w, doc.h); + + let quadPost = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), materialPost); + scenePost.add(quadPost); + + const quadHelp = new THREE.Mesh( + new THREE.PlaneGeometry(256, 256), + new THREE.MeshBasicMaterial({ + side: THREE.CullFaceFront, + depthTest: false, + depthWrite: false, + transparent: true, + map: new THREE.TextureLoader().load( __dirname + '/textures/help.png' ), + }), + ); + quadHelp.position.set(doc.w / 2 - 128, -doc.h / 2 + 128, 1); + scenePost.add(quadHelp); + + let colorQuads = createColorQuads(THREE, scenePost, palette, isSwap); + + const setPalette = (newValue) => { + palette = newValue; + materialPost.uniforms.colors.value = palette; + if (palette.length !== colorQuads.length) { + if (colorQuads) { + colorQuads.forEach((q) => scenePost.remove(q)); + } + colorQuads = createColorQuads(THREE, scenePost, palette, isSwap); + } else { + palette.forEach((color, i) => { + colorQuads[i].material.uniforms.color.value = color; + }); + } + }; + + const setModeGrayscale = (newValue) => { + if (isSwap && !newValue) { + newValue = 1; + } + modeGrayscale = newValue; + materialPost.uniforms.modeGrayscale.value = modeGrayscale; + }; + + const setIsSwap = (newValue) => { + if (isSwap && !modeGrayscale) { + setModeGrayscale(1); + } + isSwap = newValue; + materialPost.uniforms.isSwap.value = isSwap; + palette.forEach((color, i) => { + colorQuads[i].visible = isSwap; + }); + }; + + const randomizePalette = () => { + const rawPalette = generatePalette(hueModes[modeHue], numColors); + const colorPalette = rawPalette.map((c) => (new THREE.Color(...c))); + setPalette(colorPalette); + }; + + const setModeHue = (newValue) => { + modeHue = newValue; + randomizePalette(); + }; + + const setNumColors = (newValue) => { + if (numColors === newValue) { + return; + } + numColors = newValue; + + const rawPalette = generatePalette(hueModes[modeHue], numColors); + const colorPalette = rawPalette.map((c) => (new THREE.Color(...c))); + materialPost = createPostMaterial( + THREE, numColors, isSwap, modeGrayscale, colorPalette, fragmentShader, + ); + materialPost.uniforms.t.value = rt.texture; + + scenePost.remove(quadPost); + quadPost = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), materialPost); + scenePost.add(quadPost); + + randomizePalette(); + }; + + doc.on('keydown', (e) => { + if (e.keyCode === glfw.KEY_P) { + randomizePalette(); + return; + } + if (e.keyCode === glfw.KEY_M) { + setModeHue((modeHue + 1) % hueModes.length); + return; + } + if (e.keyCode === glfw.KEY_S) { + setIsSwap(!isSwap); + return; + } + if (e.keyCode === glfw.KEY_G) { + setModeGrayscale((modeGrayscale + 1) % 4); + return; + } + if (e.keyCode === Window.extraCodes[glfw.KEY_EQUAL]) { + setNumColors(Math.min(16, numColors + 1)); + return; + } + if (e.keyCode === Window.extraCodes[glfw.KEY_MINUS]) { + setNumColors(Math.max(2, numColors - 1)); + return; + } + if (e.keyCode === glfw.KEY_H || e.keyCode === Window.extraCodes[glfw.KEY_F1]) { + quadHelp.visible = !quadHelp.visible; + return; + } + // console.log('kk', e.keyCode, glfw.KEY_EQUAL, Window.extraCodes[glfw.KEY_EQUAL]); + }); + + const renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setPixelRatio(doc.devicePixelRatio); + renderer.setSize(doc.w, doc.h); + debugShaders(renderer); + + doc.on('resize', () => { + cameraPerspective.aspect = doc.w / doc.h; + cameraPerspective.updateProjectionMatrix(); + controls.update(); + + cameraOrtho.left = -doc.w / 2; + cameraOrtho.right = doc.w / 2; + cameraOrtho.top = doc.h / 2; + cameraOrtho.bottom = -doc.h / 2; + cameraOrtho.updateProjectionMatrix(); + + quadHelp.position.set(doc.w / 2 - 128, -doc.h / 2 + 128, 1); + + renderer.setSize(doc.w, doc.h); + if (rt) { + rt.dispose(); + rt = null; + } + rt = createRenderTarget(THREE, materialPost, doc.w, doc.h); + }); + + const render = () => { + const rtOld = renderer.getRenderTarget(); + renderer.setRenderTarget(rt); + renderer.render(scene, cameraPerspective); + renderer.setRenderTarget(rtOld); + + renderer.render(scenePost, cameraOrtho); + }; + + const animate = () => { + requestAnimationFrame(animate); + + controls.update(); + + if (mesh) { + mesh.rotation.y = Date.now() * 0.00005; + } + + render(); + }; + + animate(); +})(); diff --git a/examples/palette/models/LittlestTokyo.glb b/examples/palette/models/LittlestTokyo.glb new file mode 100644 index 0000000..f2c7e04 Binary files /dev/null and b/examples/palette/models/LittlestTokyo.glb differ diff --git a/examples/palette/post.glsl b/examples/palette/post.glsl new file mode 100644 index 0000000..b46d602 --- /dev/null +++ b/examples/palette/post.glsl @@ -0,0 +1,44 @@ +in vec2 tc; +out vec4 fragColor; + +#ifndef NUM_COLORS + #define NUM_COLORS 8 +#endif + +#define IDX_LAST (NUM_COLORS - 1) + +uniform bool isSwap; +uniform int modeGrayscale; +uniform sampler2D t; +uniform vec3 colors[NUM_COLORS]; + + +int getIndex(float value) { + return clamp(int(value * float(IDX_LAST) + 0.5), 0, IDX_LAST); +} + + +vec3 paletteSwap(float gray, vec3 colors[NUM_COLORS]) { + int index = getIndex(gray); + return colors[index]; +} + +float toGrayscale(vec3 rgb) { + if (modeGrayscale == 1) { + return 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; // Luminosity + } + if (modeGrayscale == 2) { + return 0.5 * (max(rgb.r, max(rgb.g, rgb.b)) + min(rgb.r, min(rgb.g, rgb.b))); // Lightness + } + return (rgb.r + rgb.g + rgb.b) * 0.3333333; // Average +} + +void main() { + vec4 rgba = texture(t, tc); + float gray = toGrayscale(rgba.rgb); + vec3 finalColor = rgba.rgb; + if (modeGrayscale > 0) { + finalColor = vec3(gray); + } + fragColor = vec4(mix(finalColor, paletteSwap(gray, colors), bvec3(isSwap)), rgba.a); +} diff --git a/examples/palette/textures/help.png b/examples/palette/textures/help.png new file mode 100644 index 0000000..58d7d31 Binary files /dev/null and b/examples/palette/textures/help.png differ diff --git a/examples/palette/textures/help.xcf b/examples/palette/textures/help.xcf new file mode 100644 index 0000000..dce7e49 Binary files /dev/null and b/examples/palette/textures/help.xcf differ diff --git a/examples/palette/utils/DRACOLoader.mjs b/examples/palette/utils/DRACOLoader.mjs new file mode 100644 index 0000000..7427dbf --- /dev/null +++ b/examples/palette/utils/DRACOLoader.mjs @@ -0,0 +1,624 @@ +import { + BufferAttribute, + BufferGeometry, + Color, + FileLoader, + Loader, + LinearSRGBColorSpace, + SRGBColorSpace +} from 'three'; + +const _taskCache = new WeakMap(); + +class DRACOLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.decoderPath = ''; + this.decoderConfig = {}; + this.decoderBinary = null; + this.decoderPending = null; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + + this.defaultAttributeIDs = { + position: 'POSITION', + normal: 'NORMAL', + color: 'COLOR', + uv: 'TEX_COORD' + }; + this.defaultAttributeTypes = { + position: 'Float32Array', + normal: 'Float32Array', + color: 'Float32Array', + uv: 'Float32Array' + }; + + } + + setDecoderPath( path ) { + + this.decoderPath = path; + + return this; + + } + + setDecoderConfig( config ) { + + this.decoderConfig = config; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, ( buffer ) => { + + this.parse( buffer, onLoad, onError ); + + }, onProgress, onError ); + + } + + parse( buffer, onLoad, onError ) { + + this.decodeDracoFile( buffer, onLoad, null, null, SRGBColorSpace ).catch( onError ); + + } + + decodeDracoFile( + buffer, callback, attributeIDs, attributeTypes, vertexColorSpace = LinearSRGBColorSpace, + ) { + + const taskConfig = { + attributeIDs: attributeIDs || this.defaultAttributeIDs, + attributeTypes: attributeTypes || this.defaultAttributeTypes, + useUniqueIDs: !! attributeIDs, + vertexColorSpace: vertexColorSpace, + }; + + return this.decodeGeometry( buffer, taskConfig ).then( callback ); + + } + + decodeGeometry( buffer, taskConfig ) { + + const taskKey = JSON.stringify( taskConfig ); + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + if ( cachedTask.key === taskKey ) { + + return cachedTask.promise; + + } else if ( buffer.byteLength === 0 ) { + + // Technically, it would be possible to wait for the previous task to complete, + // transfer the buffer back, and decode again with the second configuration. That + // is complex, and I don't know of any reason to decode a Draco buffer twice in + // different ways, so this is left unimplemented. + throw new Error( + + 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + + 'settings. Buffer has already been transferred.' + + ); + + } + + } + + // + + let worker; + const taskID = this.workerNextTaskID ++; + const taskCost = buffer.byteLength; + + // Obtain a worker and assign a task, and construct a geometry instance + // when the task completes. + const geometryPending = this._getWorker( taskID, taskCost ) + .then( ( _worker ) => { + + worker = _worker; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); + + // this.debug(); + + } ); + + } ) + .then( ( message ) => this._createGeometry( message.geometry ) ); + + // Remove task from the task list. + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + geometryPending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + this._releaseTask( worker, taskID ); + + // this.debug(); + + } + + } ); + + // Cache the task result. + _taskCache.set( buffer, { + + key: taskKey, + promise: geometryPending + + } ); + + return geometryPending; + + } + + _createGeometry( geometryData ) { + + const geometry = new BufferGeometry(); + + if ( geometryData.index ) { + + geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) ); + + } + + for ( let i = 0; i < geometryData.attributes.length; i ++ ) { + + const result = geometryData.attributes[ i ]; + const name = result.name; + const array = result.array; + const itemSize = result.itemSize; + + const attribute = new BufferAttribute( array, itemSize ); + + if ( name === 'color' ) { + + this._assignVertexColorSpace( attribute, result.vertexColorSpace ); + + attribute.normalized = ( array instanceof Float32Array ) === false; + + } + + geometry.setAttribute( name, attribute ); + + } + + return geometry; + + } + + _assignVertexColorSpace( attribute, inputColorSpace ) { + + // While .drc files do not specify colorspace, the only 'official' tooling + // is PLY and OBJ converters, which use sRGB. We'll assume sRGB when a .drc + // file is passed into .load() or .parse(). GLTFLoader uses internal APIs + // to decode geometry, and vertex colors are already Linear-sRGB in there. + + if ( inputColorSpace !== SRGBColorSpace ) return; + + const _color = new Color(); + + for ( let i = 0, il = attribute.count; i < il; i ++ ) { + + _color.fromBufferAttribute( attribute, i ).convertSRGBToLinear(); + attribute.setXYZ( i, _color.r, _color.g, _color.b ); + + } + + } + + _loadLibrary( url, responseType ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.decoderPath ); + loader.setResponseType( responseType ); + loader.setWithCredentials( this.withCredentials ); + + return new Promise( ( resolve, reject ) => { + + loader.load( url, resolve, undefined, reject ); + + } ); + + } + + preload() { + + this._initDecoder(); + + return this; + + } + + _initDecoder() { + + if ( this.decoderPending ) return this.decoderPending; + + const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; + const librariesPending = []; + + if ( useJS ) { + + librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); + + } else { + + librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); + librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); + + } + + this.decoderPending = Promise.all( librariesPending ) + .then( ( libraries ) => { + + const jsContent = libraries[ 0 ]; + + if ( ! useJS ) { + + this.decoderConfig.wasmBinary = libraries[ 1 ]; + + } + + const fn = DRACOWorker.toString(); + + const body = [ + '/* draco decoder */', + jsContent, + '', + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = new URL(`data:text/javascript;utf8,${escape(body)}`); + // this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + + } ); + + return this.decoderPending; + + } + + _getWorker( taskID, taskCost ) { + + return this._initDecoder().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new global.Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskCosts = {}; + worker._taskLoad = 0; + + worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); + + worker.on('message', function ( e ) { + + const message = e; + + switch ( message.type ) { + + case 'decode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' ); + + } + + }); + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + worker._taskCosts[ taskID ] = taskCost; + worker._taskLoad += taskCost; + return worker; + + } ); + + } + + _releaseTask( worker, taskID ) { + + worker._taskLoad -= worker._taskCosts[ taskID ]; + delete worker._callbacks[ taskID ]; + delete worker._taskCosts[ taskID ]; + + } + + debug() { + + console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; ++ i ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + if ( this.workerSourceURL !== '' ) { + + URL.revokeObjectURL( this.workerSourceURL ); + + } + + return this; + + } + +} + +/* WEB WORKER */ + +function DRACOWorker() { + + let decoderConfig; + let decoderPending; + // eslint-disable-next-line no-undef + const { parentPort } = require('worker_threads'); + global.self = global; + global.globalThis = global; + + parentPort.on('message', function ( e ) { + const message = e; + const buffer = message.buffer; + const taskConfig = message.taskConfig; + + switch ( message.type ) { + + case 'init': + decoderConfig = message.decoderConfig; + decoderPending = new Promise( function ( resolve/*, reject*/ ) { + + decoderConfig.onModuleLoaded = function ( draco ) { + + // Module is Promise-like. Wrap before resolving to avoid loop. + resolve( { draco: draco } ); + + }; + + DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef + + } ); + break; + + case 'decode': + decoderPending.then( ( module ) => { + + const draco = module.draco; + const decoder = new draco.Decoder(); + + try { + + const geometry = decodeGeometry( + draco, decoder, new Int8Array( buffer ), taskConfig, + ); + + const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); + + if ( geometry.index ) buffers.push( geometry.index.array.buffer ); + + parentPort.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); + + } catch ( error ) { + + console.error( error ); + + parentPort.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } finally { + + draco.destroy( decoder ); + + } + + } ); + break; + + } + + }); + + + + function decodeGeometry( draco, decoder, array, taskConfig ) { + + const attributeIDs = taskConfig.attributeIDs; + const attributeTypes = taskConfig.attributeTypes; + + let dracoGeometry; + let decodingStatus; + + const geometryType = decoder.GetEncodedGeometryType( array ); + + if ( geometryType === draco.TRIANGULAR_MESH ) { + + dracoGeometry = new draco.Mesh(); + decodingStatus = decoder.DecodeArrayToMesh( array, array.byteLength, dracoGeometry ); + + } else if ( geometryType === draco.POINT_CLOUD ) { + + dracoGeometry = new draco.PointCloud(); + decodingStatus = decoder.DecodeArrayToPointCloud( array, array.byteLength, dracoGeometry ); + + } else { + + throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); + + } + + if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { + + throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); + + } + + const geometry = { index: null, attributes: [] }; + + // Gather all vertex attributes. + for ( const attributeName in attributeIDs ) { + + const attributeType = global[ attributeTypes[ attributeName ] ]; + + let attribute; + let attributeID; + + // A Draco file may be created with default vertex attributes, whose attribute IDs + // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, + // a Draco file may contain a custom set of attributes, identified by known unique + // IDs. glTF files always do the latter, and `.drc` files typically do the former. + if ( taskConfig.useUniqueIDs ) { + + attributeID = attributeIDs[ attributeName ]; + attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); + + } else { + + attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); + + if ( attributeID === - 1 ) continue; + + attribute = decoder.GetAttribute( dracoGeometry, attributeID ); + + } + + const attributeResult = decodeAttribute( + draco, decoder, dracoGeometry, attributeName, attributeType, attribute, + ); + + if ( attributeName === 'color' ) { + + attributeResult.vertexColorSpace = taskConfig.vertexColorSpace; + + } + + geometry.attributes.push( attributeResult ); + + } + + // Add index. + if ( geometryType === draco.TRIANGULAR_MESH ) { + + geometry.index = decodeIndex( draco, decoder, dracoGeometry ); + + } + + draco.destroy( dracoGeometry ); + + return geometry; + + } + + function decodeIndex( draco, decoder, dracoGeometry ) { + + const numFaces = dracoGeometry.num_faces(); + const numIndices = numFaces * 3; + const byteLength = numIndices * 4; + + const ptr = draco._malloc( byteLength ); + decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr ); + const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice(); + draco._free( ptr ); + + return { array: index, itemSize: 1 }; + + } + + function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { + + const numComponents = attribute.num_components(); + const numPoints = dracoGeometry.num_points(); + const numValues = numPoints * numComponents; + const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; + const dataType = getDracoDataType( draco, attributeType ); + + const ptr = draco._malloc( byteLength ); + decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr ); + const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice(); + draco._free( ptr ); + + return { + name: attributeName, + array: array, + itemSize: numComponents + }; + + } + + function getDracoDataType( draco, attributeType ) { + + switch ( attributeType ) { + + case Float32Array: return draco.DT_FLOAT32; + case Int8Array: return draco.DT_INT8; + case Int16Array: return draco.DT_INT16; + case Int32Array: return draco.DT_INT32; + case Uint8Array: return draco.DT_UINT8; + case Uint16Array: return draco.DT_UINT16; + case Uint32Array: return draco.DT_UINT32; + + } + + } + +} + +export { DRACOLoader }; diff --git a/examples/palette/utils/create-color-quads.js b/examples/palette/utils/create-color-quads.js new file mode 100644 index 0000000..327c26e --- /dev/null +++ b/examples/palette/utils/create-color-quads.js @@ -0,0 +1,38 @@ +const createColorQuads = (THREE, scenePost, palette, isSwap) => { + const colorQuads = []; + + palette.forEach((color, i) => { + const materialColor = new THREE.ShaderMaterial({ + side: THREE.CullFaceFront, + depthTest: false, + depthWrite: false, + transparent: false, + lights: false, + uniforms: { + color: { value: color }, + }, + vertexShader: ` + void main() { + gl_Position = vec4(position, 1.0); + } + `, + fragmentShader: ` + uniform vec3 color; + void main() { + gl_FragColor = vec4(color, 1.0); + } + `, + }); + const quadColor = new THREE.Mesh( + new THREE.PlaneGeometry(0.1, 2 / palette.length), materialColor, + ); + quadColor.geometry.translate(-0.95, -1 + 2 * (i + 0.5) / palette.length, 1); + quadColor.visible = isSwap; + scenePost.add(quadColor); + colorQuads.push(quadColor); + }); + + return colorQuads; +}; + +module.exports = { createColorQuads }; diff --git a/examples/palette/utils/create-post-material.js b/examples/palette/utils/create-post-material.js new file mode 100644 index 0000000..a77f9ff --- /dev/null +++ b/examples/palette/utils/create-post-material.js @@ -0,0 +1,29 @@ +const createPostMaterial = (THREE, numColors, isSwap, modeGrayscale, palette, fragmentShader) => { + return new THREE.ShaderMaterial({ + side: THREE.CullFaceFront, + uniforms: { + isSwap: { value: isSwap }, + modeGrayscale: { value: modeGrayscale }, + t: { value: null }, + colors: { value: palette }, + }, + defines: { + NUM_COLORS: numColors, + }, + depthWrite: false, + depthTest: false, + transparent: false, + lights: false, + vertexShader: ` + out vec2 tc; + void main() { + tc = uv; + gl_Position = vec4(position, 1.0); + } + `, + fragmentShader, + glslVersion: '300 es', + }); +}; + +module.exports = { createPostMaterial }; diff --git a/examples/palette/utils/create-render-target.js b/examples/palette/utils/create-render-target.js new file mode 100644 index 0000000..675194a --- /dev/null +++ b/examples/palette/utils/create-render-target.js @@ -0,0 +1,17 @@ +const createRenderTarget = (THREE, materialPost, w, h) => { + const newRt = new THREE.WebGLRenderTarget( + w, + h, + { + minFilter: THREE.LinearFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + } + ); + + materialPost.uniforms.t.value = newRt.texture; + + return newRt; +}; + +module.exports = { createRenderTarget }; diff --git a/examples/palette/utils/debug-shaders.js b/examples/palette/utils/debug-shaders.js new file mode 100644 index 0000000..3a51702 --- /dev/null +++ b/examples/palette/utils/debug-shaders.js @@ -0,0 +1,27 @@ +const debugShaders = (renderer) => { + renderer.debug.checkShaderErrors = true; + renderer.debug.onShaderError = (gl, _program, vs, fs) => { + const parseForErrors = (shader, name) => { + const errors = (gl.getShaderInfoLog(shader) || '').trim(); + const prefix = 'Errors in ' + name + ':' + '\n\n' + errors; + + if (errors !== '') { + const code = (gl.getShaderSource(shader) || '').replace(/\t/g, ' '); + const lines = code.split('\n'); + var linedCode = ''; + var i = 1; + for (var line of lines) { + linedCode += (i < 10 ? ' ' : '') + i + ':\t\t' + line + '\n'; + i++; + } + + console.error(prefix + '\n' + linedCode); + } + }; + + parseForErrors(vs, 'Vertex Shader'); + parseForErrors(fs, 'Fragment Shader'); + }; +}; + +module.exports = { debugShaders }; diff --git a/examples/palette/utils/palette.js b/examples/palette/utils/palette.js new file mode 100644 index 0000000..044c71f --- /dev/null +++ b/examples/palette/utils/palette.js @@ -0,0 +1,117 @@ +// Derived from https://evannorton.github.io/acerolas-epic-color-palettes/ + +const oklabToLinearSrgb = (L, a, b) => { + let l_ = L + 0.3963377774 * a + 0.2158037573 * b; + let m_ = L - 0.1055613458 * a - 0.0638541728 * b; + let s_ = L - 0.0894841775 * a - 1.2914855480 * b; + + let l = l_ * l_ * l_; + let m = m_ * m_ * m_; + let s = s_ * s_ * s_; + + return [ + (+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s), + (-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s), + (-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s), + ]; +}; + +const oklchToOklab = (L, c, h) => { + return [(L), (c * Math.cos(h)), (c * Math.sin(h))]; +}; + +const lerp = (min, max, t) => { + return min + (max - min) * t; +}; + + +const generateOKLCH = (hueMode, settings) => { + let oklchColors = []; + + let hueBase = settings.hueBase * 2 * Math.PI; + let hueContrast = lerp(0.33, 1.0, settings.hueContrast); + + let chromaBase = lerp(0.01, 0.1, settings.saturationBase); + let chromaContrast = lerp(0.075, 0.125 - chromaBase, settings.saturationContrast); + let chromaFixed = lerp(0.01, 0.125, settings.fixed); + + let lightnessBase = lerp(0.3, 0.6, settings.luminanceBase); + let lightnessContrast = lerp(0.3, 1.0 - lightnessBase, settings.luminanceContrast); + let lightnessFixed = lerp(0.6, 0.9, settings.fixed); + + let chromaConstant = settings.saturationConstant; + let lightnessConstant = !chromaConstant; + + if (hueMode === 'monochromatic') { + chromaConstant = false; + lightnessConstant = false; + } + + for (let i = 0; i < settings.colorCount; ++i) { + let linearIterator = (i) / (settings.colorCount - 1); + + let hueOffset = linearIterator * hueContrast * 2 * Math.PI + (Math.PI / 4); + + if (hueMode === 'monochromatic') hueOffset *= 0.0; + if (hueMode === 'analagous') hueOffset *= 0.25; + if (hueMode === 'complementary') hueOffset *= 0.33; + if (hueMode === 'triadic') hueOffset *= 0.66; + if (hueMode === 'tetradic') hueOffset *= 0.75; + + if (hueMode !== 'monochromatic') + hueOffset += (Math.random() * 2 - 1) * 0.01; + + let chroma = chromaBase + linearIterator * chromaContrast; + let lightness = lightnessBase + linearIterator * lightnessContrast; + + if (chromaConstant) chroma = chromaFixed; + if (lightnessConstant) lightness = lightnessFixed; + + let lab = oklchToOklab(lightness, chroma, hueBase + hueOffset); + let rgb = oklabToLinearSrgb(lab[0], lab[1], lab[2]); + + rgb[0] = Math.max(0.0, Math.min(rgb[0], 1.0)); + rgb[1] = Math.max(0.0, Math.min(rgb[1], 1.0)); + rgb[2] = Math.max(0.0, Math.min(rgb[2], 1.0)); + + oklchColors.push(rgb); + } + + return oklchColors; +}; + +const createSettings = (colorCount) => { + return { + hueBase: Math.random(), + hueContrast: Math.random(), + saturationBase: Math.random(), + saturationContrast: Math.random(), + luminanceBase: Math.random(), + luminanceContrast: Math.random(), + fixed: Math.random(), + saturationConstant: true, + colorCount: colorCount, + }; +}; + +const generatePalette = (hueMode, colorCount) => { + let paletteSettings = createSettings(colorCount); + let lch = generateOKLCH(hueMode, paletteSettings); + console.log('generated', hueMode, lch); + return lch; +}; + +const hueOffsets = { + monochromatic: 0.0, + analagous: 0.25, + complementary: 0.33, + triadic: 0.66, + tetradic: 0.75, +}; + +const hueModes = Object.keys(hueOffsets); + +module.exports = { + hueModes, + generatePalette, +}; diff --git a/examples/palette/utils/populate-scene.js b/examples/palette/utils/populate-scene.js new file mode 100644 index 0000000..200f886 --- /dev/null +++ b/examples/palette/utils/populate-scene.js @@ -0,0 +1,70 @@ +const { Worker } = require('node:worker_threads'); + + +const flipUv = (attribute) => { + if (!attribute) { + return; + } + for (let i = 1; i < attribute.array.length; i += 2) { + attribute.array[i] = 1 - attribute.array[i]; + } +}; + + +const populateScene = (scene, cb) => { + global.Worker = class Worker2 extends Worker { + constructor(name, options) { + const nameStr = name.toString(); + if (nameStr.startsWith('data:')) { + const [, body] = nameStr.toString().split(','); + super(unescape(body), { ...options, eval: true }); + return; + } + + super(name, options); + } + }; + + (async () => { + const THREE = await import('three'); + const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js'); + const { DRACOLoader } = await import('./DRACOLoader.mjs'); + + const directionalLight1 = new THREE.DirectionalLight(0xffffff, 5); + directionalLight1.position.set(0.5, 0.5, 0.5).normalize(); + directionalLight1.castShadow = true; + scene.add(directionalLight1); + + const directionalLight2 = new THREE.DirectionalLight(0xffffff, 3); + directionalLight2.position.set(-0.5, 0.5, 0.5).normalize(); + scene.add(directionalLight2); + + const directionalLight3 = new THREE.DirectionalLight(0xffffff, 2); + directionalLight3.position.set(0, -0.5, -0.5).normalize(); + scene.add(directionalLight3); + + scene.background = new THREE.Color(0xbfe3dd); + + const dracoLoader = new DRACOLoader(); + dracoLoader.setDecoderPath(`${__dirname}/../../../node_modules/three/examples/jsm/libs/draco/gltf/`); + + const loader = new GLTFLoader(); + loader.setDRACOLoader(dracoLoader); + loader.load(__dirname + '/../models/LittlestTokyo.glb', (gltf) => { + gltf.scene.scale.set(0.01, 0.01, 0.01); + + gltf.scene.traverse((node) => { + if (!node.isMesh) { + return; + } + flipUv(node.geometry.attributes.uv); + flipUv(node.geometry.attributes.uv1); + }); + + scene.add(gltf.scene); + cb(gltf.scene); + }); + })(); +}; + +module.exports = { populateScene }; diff --git a/examples/srgb/main.ts b/examples/srgb/main.ts index 7664fb3..825bb57 100644 --- a/examples/srgb/main.ts +++ b/examples/srgb/main.ts @@ -1,13 +1,13 @@ -import sharp from 'sharp'; import { DataTexture, LinearSRGBColorSpace, NoToneMapping, SRGBColorSpace, Uniform, WebGLRenderer, } from 'three'; -import { init } from '../../js'; +import Img from 'image-raub'; +import { init } from '3d-core-raub'; import { SimpleTextureProcessor } from './simple_texture_processor'; -(init as (opts: unknown) => void)({ +init({ isGles3: true, isVisible: false, }); @@ -23,30 +23,6 @@ renderer.shadowMap.autoUpdate = false; renderer.outputColorSpace = LinearSRGBColorSpace; renderer.toneMapping = NoToneMapping; -renderer.debug.checkShaderErrors = true; -renderer.debug.onShaderError = (gl, _program, vs, fs) => { - const parseForErrors = (shader: WebGLShader, name: string) => { - const errors = (gl.getShaderInfoLog(shader) || '').trim(); - const prefix = 'Errors in ' + name + ':' + '\n\n' + errors; - - if (errors !== '') { - const code = (gl.getShaderSource(shader) || '').replace(/\t/g, ' '); - const lines = code.split('\n'); - var linedCode = ''; - var i = 1; - for (var line of lines) { - linedCode += (i < 10 ? ' ' : '') + i + ':\t\t' + line + '\n'; - i++; - } - - console.error(prefix + '\n' + linedCode); - } - } - - parseForErrors(vs, 'Vertex Shader'); - parseForErrors(fs, 'Fragment Shader'); -}; - const processor = new SimpleTextureProcessor(512, renderer); // A simple shader that passes through the texture @@ -76,11 +52,8 @@ function processAndSave(savePath: string, texture: DataTexture) { }); console.log(res.slice(0, 4)); - sharp(Buffer.from(res), { - raw: { width: 512, height: 512, channels: 4 }, - }).toFile(savePath, function (err: any) { - if (err) throw err; - }); + const img = Img.fromPixels(512, 512, 32, Buffer.from(res)); + img.save(savePath); } console.log('LinearSRGBColorSpace'); diff --git a/examples/srgb/package-lock.json b/examples/srgb/package-lock.json index 3af6ad9..e9c2c9b 100644 --- a/examples/srgb/package-lock.json +++ b/examples/srgb/package-lock.json @@ -7,14 +7,40 @@ "": { "name": "test", "version": "0.0.0", + "devDependencies": { + "3d-core-raub": "file:../..", + "ts-node": "^10.9.1" + } + }, + "../..": { + "version": "4.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "sharp": "^0.32.6", - "typescript": "^5.2.2" + "addon-tools-raub": "^7.3.0", + "glfw-raub": "^5.2.1", + "image-raub": "^4.1.2", + "webgl-raub": "^4.0.1" }, "devDependencies": { - "@types/node": "^20.6.5", + "@types/node": "^20.8.3", "@types/three": "0.156.0", - "ts-node": "^10.9.1" + "eslint": "^8.51.0", + "eslint-plugin-node": "^11.1.0", + "three": "0.157.0", + "typescript": "^5.2.2" + }, + "engines": { + "node": ">=18.16.0", + "npm": ">=9.5.1" + }, + "peerDependencies": { + "three": "^0.152.0" + }, + "peerDependenciesMeta": { + "three": { + "optional": true + } } }, "node_modules/@cspotcode/source-map-support": { @@ -79,34 +105,15 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz", - "integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==", - "dev": true - }, - "node_modules/@types/stats.js": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.1.tgz", - "integrity": "sha512-OgfYE1x2w1jRUXzzKABX+kOdwz2y9PE0uSwnZabkWfJTWOzm7Pvfm4JI2xqRE0q2nwUe2jZLWcrcnhd9lQU63w==", - "dev": true - }, - "node_modules/@types/three": { - "version": "0.156.0", - "resolved": "https://registry.npmjs.org/@types/three/-/three-0.156.0.tgz", - "integrity": "sha512-733bXDSRdlrxqOmQuOmfC1UBRuJ2pREPk8sWnx9MtIJEVDQMx8U0NQO5MVVaOrjzDPyLI+cFPim2X/ss9v0+LQ==", + "version": "20.8.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.3.tgz", + "integrity": "sha512-jxiZQFpb+NlH5kjW49vXxvxTjeeqlbsnTAdBTKpzEdPs9itay7MscYXz3Fo9VYFEsfQ6LJFitHad3faerLAjCw==", "dev": true, - "dependencies": { - "@types/stats.js": "*", - "@types/webxr": "*", - "fflate": "~0.6.10", - "meshoptimizer": "~0.18.1" - } + "peer": true }, - "node_modules/@types/webxr": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.5.tgz", - "integrity": "sha512-HVOsSRTQYx3zpVl0c0FBmmmcY/60BkQLzVnpE9M1aG4f2Z0aKlBWfj4XZ2zr++XNBfkQWYcwhGlmuu44RJPDqg==", - "dev": true + "node_modules/3d-core-raub": { + "resolved": "../..", + "link": true }, "node_modules/acorn": { "version": "8.10.0", @@ -135,141 +142,12 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, - "node_modules/b4a": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", - "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "engines": { - "node": ">=8" - } - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -279,396 +157,12 @@ "node": ">=0.3.1" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" - }, - "node_modules/fflate": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", - "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", - "dev": true - }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" - }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/meshoptimizer": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", - "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", - "dev": true - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" - }, - "node_modules/node-abi": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", - "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", - "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/prebuild-install/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/queue-tick": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", - "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.32.6", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", - "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", - "hasInstallScript": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.2", - "node-addon-api": "^6.1.0", - "prebuild-install": "^7.1.1", - "semver": "^7.5.4", - "simple-get": "^4.0.1", - "tar-fs": "^3.0.4", - "tunnel-agent": "^0.6.0" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/streamx": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", - "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", - "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/tar-stream": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", - "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -712,21 +206,12 @@ } } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -735,27 +220,12 @@ "node": ">=14.17" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/examples/srgb/package.json b/examples/srgb/package.json index f734cb6..d17d55a 100644 --- a/examples/srgb/package.json +++ b/examples/srgb/package.json @@ -5,13 +5,8 @@ "scripts": { "start": "npx ts-node-esm main.ts" }, - "dependencies": { - "sharp": "^0.32.6", - "typescript": "^5.2.2" - }, "devDependencies": { - "@types/node": "^20.6.5", - "@types/three": "0.156.0", + "3d-core-raub": "file:../..", "ts-node": "^10.9.1" } } diff --git a/examples/srgb/tsconfig.json b/examples/srgb/tsconfig.json index a80dbb3..6d8bc2d 100644 --- a/examples/srgb/tsconfig.json +++ b/examples/srgb/tsconfig.json @@ -6,6 +6,7 @@ "forceConsistentCasingInFileNames": true, "strict": true, "resolveJsonModule": true, + "noEmit": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, "noFallthroughCasesInSwitch": true, diff --git a/examples/three/.vs/ProjectSettings.json b/examples/three/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/examples/three/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/examples/three/.vs/VSWorkspaceState.json b/examples/three/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..6b61141 --- /dev/null +++ b/examples/three/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/examples/three/.vs/slnx.sqlite b/examples/three/.vs/slnx.sqlite new file mode 100644 index 0000000..e7dc8f8 Binary files /dev/null and b/examples/three/.vs/slnx.sqlite differ diff --git a/examples/three/.vs/three/v16/.suo b/examples/three/.vs/three/v16/.suo new file mode 100644 index 0000000..cd29938 Binary files /dev/null and b/examples/three/.vs/three/v16/.suo differ diff --git a/examples/three/points-buffer.js b/examples/three/points-buffer.js index 2e71132..e261419 100644 --- a/examples/three/points-buffer.js +++ b/examples/three/points-buffer.js @@ -3,7 +3,7 @@ console.log('https://threejs.org/examples/#webgl_points_random'); const three = require('three'); -const { init } = require('..'); +const { init } = require('../..'); const { Screen, loop, gl } = init(); diff --git a/examples/three/post.js b/examples/three/post.js deleted file mode 100644 index 18644eb..0000000 --- a/examples/three/post.js +++ /dev/null @@ -1,324 +0,0 @@ -/* eslint-disable node/no-unsupported-features/es-syntax */ -/* eslint-disable node/no-missing-import */ -/* eslint-disable max-len */ - -(async () => { - const THREE = await import('three'); - const { init, addThreeHelpers } = require('../..'); - const { - window, - document, - requestAnimationFrame, - gl, - } = init(); - addThreeHelpers(THREE, gl); - - const { RenderPass } = await import('three/examples/jsm/postprocessing/RenderPass.js'); - const { ShaderPass } = await import('three/examples/jsm/postprocessing/ShaderPass.js'); - const { BloomPass } = await import('three/examples/jsm/postprocessing/BloomPass.js'); - const { FilmPass } = await import('three/examples/jsm/postprocessing/FilmPass.js'); - const { DotScreenPass } = await import('three/examples/jsm/postprocessing/DotScreenPass.js'); - const { MaskPass, ClearMaskPass } = await import('three/examples/jsm/postprocessing/MaskPass.js'); - const { EffectComposer } = await import('three/examples/jsm/postprocessing/EffectComposer.js'); - const { TexturePass } = await import('three/examples/jsm/postprocessing/TexturePass.js'); - const { BleachBypassShader } = await import('three/examples/jsm/shaders/BleachBypassShader.js'); - const { ColorifyShader } = await import('three/examples/jsm/shaders/ColorifyShader.js'); - const { HorizontalBlurShader } = await import('three/examples/jsm/shaders/HorizontalBlurShader.js'); - const { VerticalBlurShader } = await import('three/examples/jsm/shaders/VerticalBlurShader.js'); - const { SepiaShader } = await import('three/examples/jsm/shaders/SepiaShader.js'); - const { VignetteShader } = await import('three/examples/jsm/shaders/VignetteShader.js'); - // const { GammaCorrectionShader } = await import('three/examples/jsm/shaders/GammaCorrectionShader.js'); - const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js'); - - let container; - - let composerScene, composer1, composer2, composer3, composer4; - - let cameraOrtho, cameraPerspective, sceneModel, sceneBG, renderer, mesh, directionalLight; - - const width = window.innerWidth || 2; - const height = window.innerHeight || 2; - - let halfWidth = width / 2; - let halfHeight = height / 2; - - let quadBG, quadMask, renderScene; - - const delta = 0.01; - - initExample(); - animate(); - - function initExample() { - - container = document.getElementById( 'container' ); - - // - - cameraOrtho = new THREE.OrthographicCamera( - halfWidth, halfWidth, halfHeight, - halfHeight, - 10000, 10000 ); - cameraOrtho.position.z = 100; - - cameraPerspective = new THREE.PerspectiveCamera( 50, width / height, 1, 10000 ); - cameraPerspective.position.z = 900; - - // - - sceneModel = new THREE.Scene(); - sceneBG = new THREE.Scene(); - - // - - directionalLight = new THREE.DirectionalLight( 0xffffff, 3 ); - directionalLight.position.set( 0, - 0.1, 1 ).normalize(); - sceneModel.add( directionalLight ); - - const loader = new GLTFLoader(); - loader.load( __dirname + '/models/LeePerrySmith.glb', function ( gltf ) { - - createMesh( gltf.scene.children[ 0 ].geometry, sceneModel, 100 ); - - } ); - - // - - const diffuseMap = new THREE.TextureLoader().load( __dirname + '/textures/pz.jpg' ); - // diffuseMap.colorSpace = THREE.SRGBColorSpace; - - const materialColor = new THREE.MeshBasicMaterial( { - map: diffuseMap, - depthTest: false - } ); - - quadBG = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), materialColor ); - quadBG.position.z = - 500; - quadBG.scale.set( width, height, 1 ); - sceneBG.add( quadBG ); - - // - - const sceneMask = new THREE.Scene(); - - quadMask = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), new THREE.MeshBasicMaterial( { color: 0xffaa00 } ) ); - quadMask.position.z = - 300; - quadMask.scale.set( width / 2, height / 2, 1 ); - sceneMask.add( quadMask ); - - // - - renderer = new THREE.WebGLRenderer(); - renderer.setPixelRatio( window.devicePixelRatio ); - renderer.setSize( width, height ); - renderer.autoClear = false; - - // - - container.appendChild( renderer.domElement ); - - // - - const shaderBleach = BleachBypassShader; - const shaderSepia = SepiaShader; - const shaderVignette = VignetteShader; - - const effectBleach = new ShaderPass( shaderBleach ); - const effectSepia = new ShaderPass( shaderSepia ); - const effectVignette = new ShaderPass( shaderVignette ); - // const gammaCorrection = new ShaderPass( GammaCorrectionShader ); - - effectBleach.uniforms[ 'opacity' ].value = 0.95; - - effectSepia.uniforms[ 'amount' ].value = 0.9; - - effectVignette.uniforms[ 'offset' ].value = 0.95; - effectVignette.uniforms[ 'darkness' ].value = 1.6; - - const effectBloom = new BloomPass( 0.5 ); - const effectFilm = new FilmPass( 0.35 ); - const effectFilmBW = new FilmPass( 0.35, true ); - const effectDotScreen = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); - - const effectHBlur = new ShaderPass( HorizontalBlurShader ); - const effectVBlur = new ShaderPass( VerticalBlurShader ); - effectHBlur.uniforms[ 'h' ].value = 2 / ( width / 2 ); - effectVBlur.uniforms[ 'v' ].value = 2 / ( height / 2 ); - - const effectColorify1 = new ShaderPass( ColorifyShader ); - const effectColorify2 = new ShaderPass( ColorifyShader ); - effectColorify1.uniforms[ 'color' ] = new THREE.Uniform( new THREE.Color( 1, 0.8, 0.8 ) ); - effectColorify2.uniforms[ 'color' ] = new THREE.Uniform( new THREE.Color( 1, 0.75, 0.5 ) ); - - const clearMask = new ClearMaskPass(); - const renderMask = new MaskPass( sceneModel, cameraPerspective ); - const renderMaskInverse = new MaskPass( sceneModel, cameraPerspective ); - - renderMaskInverse.inverse = true; - - // - - const rtParameters = { - stencilBuffer: true - }; - - const rtWidth = width / 2; - const rtHeight = height / 2; - - // - - const renderBackground = new RenderPass( sceneBG, cameraOrtho ); - const renderModel = new RenderPass( sceneModel, cameraPerspective ); - - renderModel.clear = false; - - composerScene = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth * 2, rtHeight * 2, rtParameters ) ); - - composerScene.addPass( renderBackground ); - composerScene.addPass( renderModel ); - composerScene.addPass( renderMaskInverse ); - composerScene.addPass( effectHBlur ); - composerScene.addPass( effectVBlur ); - composerScene.addPass( clearMask ); - - // - - renderScene = new TexturePass( composerScene.renderTarget2.texture ); - - // - - composer1 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); - - composer1.addPass( renderScene ); - // composer1.addPass( gammaCorrection ); - composer1.addPass( effectFilmBW ); - composer1.addPass( effectVignette ); - - // - - composer2 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); - - composer2.addPass( renderScene ); - // composer2.addPass( gammaCorrection ); - composer2.addPass( effectDotScreen ); - composer2.addPass( renderMask ); - composer2.addPass( effectColorify1 ); - composer2.addPass( clearMask ); - composer2.addPass( renderMaskInverse ); - composer2.addPass( effectColorify2 ); - composer2.addPass( clearMask ); - composer2.addPass( effectVignette ); - - // - - composer3 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); - - composer3.addPass( renderScene ); - // composer3.addPass( gammaCorrection ); - composer3.addPass( effectSepia ); - composer3.addPass( effectFilm ); - composer3.addPass( effectVignette ); - - // - - composer4 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); - - composer4.addPass( renderScene ); - // composer4.addPass( gammaCorrection ); - composer4.addPass( effectBloom ); - composer4.addPass( effectFilm ); - composer4.addPass( effectBleach ); - composer4.addPass( effectVignette ); - - renderScene.uniforms[ 'tDiffuse' ].value = composerScene.renderTarget2.texture; - - window.addEventListener( 'resize', onWindowResize ); - - } - - function onWindowResize() { - - halfWidth = window.innerWidth / 2; - halfHeight = window.innerHeight / 2; - - cameraPerspective.aspect = window.innerWidth / window.innerHeight; - cameraPerspective.updateProjectionMatrix(); - - cameraOrtho.left = - halfWidth; - cameraOrtho.right = halfWidth; - cameraOrtho.top = halfHeight; - cameraOrtho.bottom = - halfHeight; - - cameraOrtho.updateProjectionMatrix(); - - renderer.setSize( window.innerWidth, window.innerHeight ); - - composerScene.setSize( halfWidth * 2, halfHeight * 2 ); - - composer1.setSize( halfWidth, halfHeight ); - composer2.setSize( halfWidth, halfHeight ); - composer3.setSize( halfWidth, halfHeight ); - composer4.setSize( halfWidth, halfHeight ); - - renderScene.uniforms[ 'tDiffuse' ].value = composerScene.renderTarget2.texture; - - quadBG.scale.set( window.innerWidth, window.innerHeight, 1 ); - quadMask.scale.set( window.innerWidth / 2, window.innerHeight / 2, 1 ); - - } - - function createMesh( geometry, scene, scale ) { - - const diffuseMap = new THREE.TextureLoader().load( __dirname + '/textures/Map-COL.jpg' ); - // diffuseMap.colorSpace = THREE.SRGBColorSpace; - - const mat2 = new THREE.MeshPhongMaterial( { - - color: 0xcbcbcb, - specular: 0x080808, - shininess: 20, - map: diffuseMap, - normalMap: new THREE.TextureLoader().load( __dirname + '/textures/Infinite-Level_02_Tangent_SmoothUV.jpg' ), - normalScale: new THREE.Vector2( 0.75, 0.75 ) - - } ); - - mesh = new THREE.Mesh( geometry, mat2 ); - mesh.position.set( 0, - 50, 0 ); - mesh.scale.set( scale, scale, scale ); - - scene.add( mesh ); - - } - - // - - function animate() { - - requestAnimationFrame( animate ); - render(); - - } - - function render() { - - const time = Date.now() * 0.0004; - - if ( mesh ) mesh.rotation.y = - time; - - renderer.setViewport( 0, 0, halfWidth, halfHeight ); - composerScene.render( delta ); - - renderer.setViewport( 0, 0, halfWidth, halfHeight ); - composer1.render( delta ); - - renderer.setViewport( halfWidth, 0, halfWidth, halfHeight ); - composer2.render( delta ); - - renderer.setViewport( 0, halfHeight, halfWidth, halfHeight ); - composer3.render( delta ); - - renderer.setViewport( halfWidth, halfHeight, halfWidth, halfHeight ); - composer4.render( delta ); - - } - - -})(); \ No newline at end of file diff --git a/examples/three/post.mjs b/examples/three/post.mjs new file mode 100644 index 0000000..47aa4a5 --- /dev/null +++ b/examples/three/post.mjs @@ -0,0 +1,325 @@ +/* eslint-disable max-len */ +// Based on https://threejs.org/examples/?q=postprocess#webgl_postprocessing_advanced + +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import * as THREE from 'three'; +import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; +import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; +import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass.js'; +import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js'; +import { DotScreenPass } from 'three/examples/jsm/postprocessing/DotScreenPass.js'; +import { MaskPass, ClearMaskPass } from 'three/examples/jsm/postprocessing/MaskPass.js'; +import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'; +import { TexturePass } from 'three/examples/jsm/postprocessing/TexturePass.js'; +import { BleachBypassShader } from 'three/examples/jsm/shaders/BleachBypassShader.js'; +import { ColorifyShader } from 'three/examples/jsm/shaders/ColorifyShader.js'; +import { HorizontalBlurShader } from 'three/examples/jsm/shaders/HorizontalBlurShader.js'; +import { VerticalBlurShader } from 'three/examples/jsm/shaders/VerticalBlurShader.js'; +import { SepiaShader } from 'three/examples/jsm/shaders/SepiaShader.js'; +import { VignetteShader } from 'three/examples/jsm/shaders/VignetteShader.js'; +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; + +import { init, addThreeHelpers } from '../../index.js'; + + +const __dirname = dirname(fileURLToPath(import.meta.url)); + + +const { + window, + document, + requestAnimationFrame, + gl, +} = init(); +addThreeHelpers(THREE, gl); + +let container; + +let composerScene, composer1, composer2, composer3, composer4; + +let cameraOrtho, cameraPerspective, sceneModel, sceneBG, renderer, mesh, directionalLight; + +const width = window.innerWidth || 2; +const height = window.innerHeight || 2; + +let halfWidth = width / 2; +let halfHeight = height / 2; + +let quadBG, quadMask, renderScene; + +const delta = 0.01; + +initExample(); +animate(); + +function initExample() { + + container = document.getElementById( 'container' ); + + // + + cameraOrtho = new THREE.OrthographicCamera( - halfWidth, halfWidth, halfHeight, - halfHeight, - 10000, 10000 ); + cameraOrtho.position.z = 100; + + cameraPerspective = new THREE.PerspectiveCamera( 50, width / height, 1, 10000 ); + cameraPerspective.position.z = 900; + + // + + sceneModel = new THREE.Scene(); + sceneBG = new THREE.Scene(); + + // + + directionalLight = new THREE.DirectionalLight( 0xffffff, 3 ); + directionalLight.position.set( 0, - 0.1, 1 ).normalize(); + sceneModel.add( directionalLight ); + + const loader = new GLTFLoader(); + loader.load( __dirname + '/models/LeePerrySmith.glb', function ( gltf ) { + + createMesh( gltf.scene.children[ 0 ].geometry, sceneModel, 100 ); + + } ); + + // + + const diffuseMap = new THREE.TextureLoader().load( __dirname + '/textures/pz.jpg' ); + // diffuseMap.colorSpace = THREE.SRGBColorSpace; + + const materialColor = new THREE.MeshBasicMaterial( { + map: diffuseMap, + depthTest: false + } ); + + quadBG = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), materialColor ); + quadBG.position.z = - 500; + quadBG.scale.set( width, height, 1 ); + sceneBG.add( quadBG ); + + // + + const sceneMask = new THREE.Scene(); + + quadMask = new THREE.Mesh( new THREE.PlaneGeometry( 1, 1 ), new THREE.MeshBasicMaterial( { color: 0xffaa00 } ) ); + quadMask.position.z = - 300; + quadMask.scale.set( width / 2, height / 2, 1 ); + sceneMask.add( quadMask ); + + // + + renderer = new THREE.WebGLRenderer(); + renderer.setPixelRatio( window.devicePixelRatio ); + renderer.setSize( width, height ); + renderer.autoClear = false; + + // + + container.appendChild( renderer.domElement ); + + // + + const shaderBleach = BleachBypassShader; + const shaderSepia = SepiaShader; + const shaderVignette = VignetteShader; + + const effectBleach = new ShaderPass( shaderBleach ); + const effectSepia = new ShaderPass( shaderSepia ); + const effectVignette = new ShaderPass( shaderVignette ); + // const gammaCorrection = new ShaderPass( GammaCorrectionShader ); + + effectBleach.uniforms[ 'opacity' ].value = 0.95; + + effectSepia.uniforms[ 'amount' ].value = 0.9; + + effectVignette.uniforms[ 'offset' ].value = 0.95; + effectVignette.uniforms[ 'darkness' ].value = 1.6; + + const effectBloom = new BloomPass( 0.5 ); + const effectFilm = new FilmPass( 0.35 ); + const effectFilmBW = new FilmPass( 0.35, true ); + const effectDotScreen = new DotScreenPass( new THREE.Vector2( 0, 0 ), 0.5, 0.8 ); + + const effectHBlur = new ShaderPass( HorizontalBlurShader ); + const effectVBlur = new ShaderPass( VerticalBlurShader ); + effectHBlur.uniforms[ 'h' ].value = 2 / ( width / 2 ); + effectVBlur.uniforms[ 'v' ].value = 2 / ( height / 2 ); + + const effectColorify1 = new ShaderPass( ColorifyShader ); + const effectColorify2 = new ShaderPass( ColorifyShader ); + effectColorify1.uniforms[ 'color' ] = new THREE.Uniform( new THREE.Color( 1, 0.8, 0.8 ) ); + effectColorify2.uniforms[ 'color' ] = new THREE.Uniform( new THREE.Color( 1, 0.75, 0.5 ) ); + + const clearMask = new ClearMaskPass(); + const renderMask = new MaskPass( sceneModel, cameraPerspective ); + const renderMaskInverse = new MaskPass( sceneModel, cameraPerspective ); + + renderMaskInverse.inverse = true; + + // + + const rtParameters = { + stencilBuffer: true + }; + + const rtWidth = width / 2; + const rtHeight = height / 2; + + // + + const renderBackground = new RenderPass( sceneBG, cameraOrtho ); + const renderModel = new RenderPass( sceneModel, cameraPerspective ); + + renderModel.clear = false; + + composerScene = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth * 2, rtHeight * 2, rtParameters ) ); + + composerScene.addPass( renderBackground ); + composerScene.addPass( renderModel ); + composerScene.addPass( renderMaskInverse ); + composerScene.addPass( effectHBlur ); + composerScene.addPass( effectVBlur ); + composerScene.addPass( clearMask ); + + // + + renderScene = new TexturePass( composerScene.renderTarget2.texture ); + + // + + composer1 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); + + composer1.addPass( renderScene ); + // composer1.addPass( gammaCorrection ); + composer1.addPass( effectFilmBW ); + composer1.addPass( effectVignette ); + + // + + composer2 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); + + composer2.addPass( renderScene ); + // composer2.addPass( gammaCorrection ); + composer2.addPass( effectDotScreen ); + composer2.addPass( renderMask ); + composer2.addPass( effectColorify1 ); + composer2.addPass( clearMask ); + composer2.addPass( renderMaskInverse ); + composer2.addPass( effectColorify2 ); + composer2.addPass( clearMask ); + composer2.addPass( effectVignette ); + + // + + composer3 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); + + composer3.addPass( renderScene ); + // composer3.addPass( gammaCorrection ); + composer3.addPass( effectSepia ); + composer3.addPass( effectFilm ); + composer3.addPass( effectVignette ); + + // + + composer4 = new EffectComposer( renderer, new THREE.WebGLRenderTarget( rtWidth, rtHeight, rtParameters ) ); + + composer4.addPass( renderScene ); + // composer4.addPass( gammaCorrection ); + composer4.addPass( effectBloom ); + composer4.addPass( effectFilm ); + composer4.addPass( effectBleach ); + composer4.addPass( effectVignette ); + + renderScene.uniforms[ 'tDiffuse' ].value = composerScene.renderTarget2.texture; + + window.addEventListener( 'resize', onWindowResize ); + +} + +function onWindowResize() { + + halfWidth = window.innerWidth / 2; + halfHeight = window.innerHeight / 2; + + cameraPerspective.aspect = window.innerWidth / window.innerHeight; + cameraPerspective.updateProjectionMatrix(); + + cameraOrtho.left = - halfWidth; + cameraOrtho.right = halfWidth; + cameraOrtho.top = halfHeight; + cameraOrtho.bottom = - halfHeight; + + cameraOrtho.updateProjectionMatrix(); + + renderer.setSize( window.innerWidth, window.innerHeight ); + + composerScene.setSize( halfWidth * 2, halfHeight * 2 ); + + composer1.setSize( halfWidth, halfHeight ); + composer2.setSize( halfWidth, halfHeight ); + composer3.setSize( halfWidth, halfHeight ); + composer4.setSize( halfWidth, halfHeight ); + + renderScene.uniforms[ 'tDiffuse' ].value = composerScene.renderTarget2.texture; + + quadBG.scale.set( window.innerWidth, window.innerHeight, 1 ); + quadMask.scale.set( window.innerWidth / 2, window.innerHeight / 2, 1 ); + +} + +function createMesh( geometry, scene, scale ) { + + const diffuseMap = new THREE.TextureLoader().load( __dirname + '/textures/Map-COL.jpg' ); + // diffuseMap.colorSpace = THREE.SRGBColorSpace; + + const mat2 = new THREE.MeshPhongMaterial( { + + color: 0xcbcbcb, + specular: 0x080808, + shininess: 20, + map: diffuseMap, + normalMap: new THREE.TextureLoader().load( __dirname + '/textures/Infinite-Level_02_Tangent_SmoothUV.jpg' ), + normalScale: new THREE.Vector2( 0.75, 0.75 ) + + } ); + + mesh = new THREE.Mesh( geometry, mat2 ); + mesh.position.set( 0, - 50, 0 ); + mesh.scale.set( scale, scale, scale ); + + scene.add( mesh ); + +} + +// + +function animate() { + + requestAnimationFrame( animate ); + render(); + +} + +function render() { + + const time = Date.now() * 0.0004; + + if ( mesh ) mesh.rotation.y = - time; + + renderer.setViewport( 0, 0, halfWidth, halfHeight ); + composerScene.render( delta ); + + renderer.setViewport( 0, 0, halfWidth, halfHeight ); + composer1.render( delta ); + + renderer.setViewport( halfWidth, 0, halfWidth, halfHeight ); + composer2.render( delta ); + + renderer.setViewport( 0, halfHeight, halfWidth, halfHeight ); + composer3.render( delta ); + + renderer.setViewport( halfWidth, halfHeight, halfWidth, halfHeight ); + composer4.render( delta ); + +} diff --git a/index.d.ts b/index.d.ts index 07aa3a1..9ff7785 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,13 +1,27 @@ -import glfw, { Document, Window } from 'glfw-raub'; -import webgl from 'webgl-raub'; -import { Image } from 'image-raub'; - -import WebVRManager from './js/core/vr-manager'; - - declare module "3d-core-raub" { + type Image = import('image-raub'); + type TThree = typeof import('three'); + type TWebgl = typeof import('webgl-raub'); + type TGlfw = typeof import('glfw-raub'); + type TDocumentOpts = import('glfw-raub').TDocumentOpts; + type Document = import('glfw-raub').Document; + type Window = import('glfw-raub').Window; + + type TUnknownObject = Readonly<{ [id: string]: unknown }>; + class WebVRManager { + readonly enabled: boolean; + + constructor(); + + isPresenting(): boolean; + dispose(): void; + setAnimationLoop(): void; + getCamera(): TUnknownObject; + submitFrame(): void; + } + type TLocation = Readonly<{ href: string, ancestorOrigins: TUnknownObject, @@ -28,7 +42,7 @@ declare module "3d-core-raub" { bluetooth: TUnknownObject, clipboard: TUnknownObject, connection: { - onchange: null, + onchange: unknown, effectiveType: string, rtt: number, downlink: number, @@ -37,7 +51,7 @@ declare module "3d-core-raub" { cookieEnabled: boolean, credentials: TUnknownObject, deviceMemory: number, - doNotTrack: null, + doNotTrack: unknown, geolocation: TUnknownObject, hardwareConcurrency: number, keyboard: TUnknownObject, @@ -46,25 +60,25 @@ declare module "3d-core-raub" { locks: TUnknownObject, maxTouchPoints: number, mediaCapabilities: TUnknownObject, - mediaDevices: { ondevicechange: null }, + mediaDevices: { ondevicechange: unknown }, mimeTypes: { length: number }, onLine: boolean, permissions: TUnknownObject, platform: string, plugins: { length: number }, - presentation: { defaultRequest: null, receiver: null }, + presentation: { defaultRequest: unknown, receiver: unknown }, product: string, productSub: string, serviceWorker: { ready: Promise, - controller: null, - oncontrollerchange: null, - onmessage: null + controller: unknown, + oncontrollerchange: unknown, + onmessage: unknown }, storage: TUnknownObject, usb: { - onconnect: null, - ondisconnect: null, + onconnect: unknown, + ondisconnect: unknown, }, userAgent: string, vendor: string, @@ -99,13 +113,13 @@ declare module "3d-core-raub" { * A WebGL context instance. This is **almost** the same as real WebGL stuff. * For more info see [webgl-raub](https://github.com/node-3d/webgl-raub#webgl-for-nodejs). */ - gl: typeof webgl, + gl: TWebgl, /** * Low level GLFW interface. * For more info see glfw-raub](https://github.com/node-3d/glfw-raub#glfw-for-nodejs) */ - glfw: typeof glfw, + glfw: TGlfw, /** * The default instance of Document - created automatically when `init()` is called. @@ -139,7 +153,7 @@ declare module "3d-core-raub" { type TPluginDecl = string | ((core3d: TCore3D) => void) | Readonly<{ name: string, opts: TUnknownObject }>; - type TInitOpts = ConstructorParameters[0] & Readonly<{ + type TInitOpts = TDocumentOpts & Readonly<{ /** * Use GLES 3.2 profile instead of default. * @@ -161,10 +175,18 @@ declare module "3d-core-raub" { */ isWebGL2?: boolean, + /** + * Is default window visible? + * + * For "headless" mode, use `false`. The window will be created in GLFW hidden mode + * (this is how headless GL works anyway). The default value is `true` - visible window. + */ + isVisible?: boolean, + /** * An override for WebGL implementation. */ - webgl?: typeof webgl, + webgl?: TWebgl, /** * An override for Image implementation. @@ -174,7 +196,7 @@ declare module "3d-core-raub" { /** * An override for GLFW implementation. */ - glfw?: typeof glfw, + glfw?: TGlfw, /** * An override for the `location` object. @@ -203,5 +225,5 @@ declare module "3d-core-raub" { * Teaches `three.FileLoader.load` to work with Node `fs`. Additionally implements * `three.Texture.fromId` static method to create THREE textures from known GL resource IDs. */ - export const addThreeHelpers: (three: TUnknownObject, gl: typeof webgl) => void; + export const addThreeHelpers: (three: TThree, gl: TWebgl) => void; } diff --git a/js/core/threejs-helpers.js b/js/core/threejs-helpers.js index dee9d1f..58ac1a7 100644 --- a/js/core/threejs-helpers.js +++ b/js/core/threejs-helpers.js @@ -1,14 +1,56 @@ 'use strict'; +const { Blob } = require('node:buffer'); + + +const finishLoad = (three, responseType, mimeType, onLoad, buffer) => { + if (responseType === 'arraybuffer') { + onLoad((new Uint8Array(buffer)).buffer); + return; + } + + if (responseType === 'blob') { + onLoad(new Blob([buffer])); + return; + } + + if (responseType === 'document') { + onLoad({}); + return; + } + + if (responseType === 'json') { + try { + onLoad(JSON.parse(buffer.toString())); + } catch (e) { + onLoad({}); + } + return; + } + + if (!mimeType) { + onLoad(buffer.toString()); + return; + } + + // sniff encoding + const re = /charset="?([^;"\s]*)"?/i; + const exec = re.exec(mimeType); + const label = exec && exec[1] ? exec[1].toLowerCase() : undefined; + const decoder = new three.TextDecoder(label); + + onLoad(decoder.decode(buffer)); +}; + module.exports = (three, gl) => { - three.FileLoader.prototype.load = (url, onLoad, onProgress, onError) => { + three.FileLoader.prototype.load = function (url, onLoad, onProgress, onError) { // Data URI if (/^data:/.test(url)) { const [head, body] = url.split(','); const isBase64 = head.indexOf('base64') > -1; const data = isBase64 ? Buffer.from(body, 'base64') : Buffer.from(unescape(body)); - onLoad(data); + finishLoad(three, this.responseType, this.mimeType, onLoad, data); return; } @@ -17,7 +59,7 @@ module.exports = (three, gl) => { const download = require('addon-tools-raub/download'); download(url).then( - (data) => onLoad(data), + (data) => finishLoad(three, this.responseType, this.mimeType, onLoad, data), (err) => typeof onError === 'function' ? onError(err) : console.error(err) ); @@ -25,11 +67,14 @@ module.exports = (three, gl) => { } // Filesystem URI + if (this.path !== undefined) { + url = this.path + url; + } require('fs').readFile(url, (err, data) => { if (err) { return typeof onError === 'function' ? onError(err) : console.error(err); } - onLoad((new Uint8Array(data)).buffer); + finishLoad(three, this.responseType, this.mimeType, onLoad, data); }); }; diff --git a/package-lock.json b/package-lock.json index 74f0aeb..d22c9b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,31 +1,33 @@ { "name": "3d-core-raub", - "version": "4.0.0", + "version": "4.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "3d-core-raub", - "version": "4.0.0", + "version": "4.0.2", "license": "MIT", "dependencies": { - "addon-tools-raub": "^7.2.0", - "glfw-raub": "^5.2.0", - "image-raub": "^4.1.1", - "webgl-raub": "^4.0.0" + "addon-tools-raub": "^7.4.0", + "glfw-raub": "^5.2.2", + "image-raub": "^4.1.3", + "webgl-raub": "^4.0.2" }, "devDependencies": { - "eslint": "^8.40.0", + "@types/node": "^20.8.3", + "@types/three": "0.156.0", + "eslint": "^8.51.0", "eslint-plugin-node": "^11.1.0", "three": "0.157.0", - "typescript": "^5.0.4" + "typescript": "^5.2.2" }, "engines": { "node": ">=18.16.0", "npm": ">=9.5.1" }, "peerDependencies": { - "three": "^0.157.0" + "three": "^0.152.0" }, "peerDependenciesMeta": { "three": { @@ -166,6 +168,39 @@ "node": ">= 8" } }, + "node_modules/@types/node": { + "version": "20.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", + "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", + "dev": true, + "dependencies": { + "undici-types": "~5.25.1" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.1.tgz", + "integrity": "sha512-OgfYE1x2w1jRUXzzKABX+kOdwz2y9PE0uSwnZabkWfJTWOzm7Pvfm4JI2xqRE0q2nwUe2jZLWcrcnhd9lQU63w==", + "dev": true + }, + "node_modules/@types/three": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.156.0.tgz", + "integrity": "sha512-733bXDSRdlrxqOmQuOmfC1UBRuJ2pREPk8sWnx9MtIJEVDQMx8U0NQO5MVVaOrjzDPyLI+cFPim2X/ss9v0+LQ==", + "dev": true, + "dependencies": { + "@types/stats.js": "*", + "@types/webxr": "*", + "fflate": "~0.6.10", + "meshoptimizer": "~0.18.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.5.tgz", + "integrity": "sha512-HVOsSRTQYx3zpVl0c0FBmmmcY/60BkQLzVnpE9M1aG4f2Z0aKlBWfj4XZ2zr++XNBfkQWYcwhGlmuu44RJPDqg==", + "dev": true + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -188,15 +223,15 @@ } }, "node_modules/addon-tools-raub": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/addon-tools-raub/-/addon-tools-raub-7.2.1.tgz", - "integrity": "sha512-IulOFBTjBz2gsd4JE9yzbaqBD8MY+hFU8d+mia6rf0onXpw3A8YDQMRbZ614P0Grk7KZDaRzbkS62cFgIc7ZQg==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/addon-tools-raub/-/addon-tools-raub-7.4.0.tgz", + "integrity": "sha512-KOYJFeeI/EntAcho4/h7dDWNd1jV1atod6xEwcfmArifKga6tR2Nd9eEBCx3E6Ju6sTEpoaMNg3WBB9AFJPhxg==", "engines": { "node": ">=18.16.0", "npm": ">=9.5.1" }, "peerDependencies": { - "node-addon-api": "^6.1.0" + "node-addon-api": "^7.0.0" }, "peerDependenciesMeta": { "node-addon-api": { @@ -353,12 +388,12 @@ "dev": true }, "node_modules/deps-freeimage-raub": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/deps-freeimage-raub/-/deps-freeimage-raub-4.1.0.tgz", - "integrity": "sha512-vg2Gx7QOvVS9Aqzo2ZZaFAPqp5nkqaBXR9nuCL4rrCja40LanlJ+f9D/N3nnOhWTURNAD87BmMlvjvrglU94RA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/deps-freeimage-raub/-/deps-freeimage-raub-4.1.1.tgz", + "integrity": "sha512-GNg8GvXFgaqkIRf55p0L1iZ80tHc3lBfiDamWCUjVNiUEGE15qC72OsAtVrBgcWnRTv/J0JHA/76u0hoN/ic/Q==", "hasInstallScript": true, "dependencies": { - "addon-tools-raub": "^7.2.0" + "addon-tools-raub": "^7.3.0" }, "engines": { "node": ">=18.16.0", @@ -366,12 +401,12 @@ } }, "node_modules/deps-opengl-raub": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/deps-opengl-raub/-/deps-opengl-raub-5.1.0.tgz", - "integrity": "sha512-t9LOtcnL7QTyqMociTh5f4jf/dcatkXiKpHwDSK7c7twtD9eP4guQivhwq4nSKx4dEAapsSvNqrrSGxx0v9cbw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/deps-opengl-raub/-/deps-opengl-raub-5.1.1.tgz", + "integrity": "sha512-M7SeQuckAe1P7pu8PXxpwoc7KOyIjcN8Wa6IQYmhwYYXpZkTKkB3LgpexXIj07rT4WMUuvuFUjOZQzhIdqbmrw==", "hasInstallScript": true, "dependencies": { - "addon-tools-raub": "^7.2.0" + "addon-tools-raub": "^7.3.0" }, "engines": { "node": ">=18.16.0", @@ -633,6 +668,12 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "dev": true + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -688,14 +729,14 @@ "dev": true }, "node_modules/glfw-raub": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/glfw-raub/-/glfw-raub-5.2.0.tgz", - "integrity": "sha512-vTdLL2UezLx4tMXE21N3O4uZ5OhyUPCO3BYVxRtUJP7hOoYwqSGgYrrieu8N4Kx7wf18gngEoR2W0aAuQ0/60Q==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/glfw-raub/-/glfw-raub-5.2.2.tgz", + "integrity": "sha512-tKyH2V68LEyPQj1VVRk4e6wylGlj6a7rAC/8uf1MJQzg1dccnS/gFtxF6P1MRUOXa8KqACYd0vIvTpThiBtDCw==", "hasInstallScript": true, "dependencies": { - "addon-tools-raub": "^7.2.0", - "deps-opengl-raub": "^5.1.0", - "segfault-raub": "^2.1.0" + "addon-tools-raub": "^7.4.0", + "deps-opengl-raub": "^5.1.1", + "segfault-raub": "^2.1.2" }, "engines": { "node": ">=18.16.0", @@ -783,14 +824,14 @@ } }, "node_modules/image-raub": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/image-raub/-/image-raub-4.1.1.tgz", - "integrity": "sha512-zI/n6cJv/tWb583jSMUQPt0acoearl73LJBG6QULzAMAI1Lg7tNJvfD293zh3yap5ELrAYstOAKGrTzWcGEvNQ==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/image-raub/-/image-raub-4.1.3.tgz", + "integrity": "sha512-a8e15J/oWles07b4Ju9Iy6RrJ2uJKQeoi4zqE4wvH24iDSEgsYXoO6nJpET2f26gaqeJRFA7ZTsUK2xupNyj4A==", "hasInstallScript": true, "dependencies": { - "addon-tools-raub": "^7.2.0", - "deps-freeimage-raub": "^4.1.0", - "segfault-raub": "^2.1.0" + "addon-tools-raub": "^7.4.0", + "deps-freeimage-raub": "^4.1.1", + "segfault-raub": "^2.1.2" }, "engines": { "node": ">=18.16.0", @@ -917,9 +958,9 @@ "dev": true }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -959,6 +1000,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/meshoptimizer": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz", + "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", + "dev": true + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1135,9 +1182,9 @@ } }, "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", @@ -1209,12 +1256,12 @@ } }, "node_modules/segfault-raub": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/segfault-raub/-/segfault-raub-2.1.0.tgz", - "integrity": "sha512-sULCxoOc7ZTz6pzR+PmCUDQ6rt3dthGScTwxx0fsCCEWMb1xrJEGMFFYxQPmDtGpqNF3HqZQSgK4uyeeFGLRYw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/segfault-raub/-/segfault-raub-2.1.2.tgz", + "integrity": "sha512-W8qsXWceJfJBkgZad6ORZ6rNlMmK40XRsC3JX3bJgfEMQLb9wSfaIvzE+pB+frFFYcww83C8Ag0XtheEHiS5wA==", "hasInstallScript": true, "dependencies": { - "addon-tools-raub": "^7.2.0" + "addon-tools-raub": "^7.3.0" }, "engines": { "node": ">=18.16.0", @@ -1348,6 +1395,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "dev": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1358,14 +1411,14 @@ } }, "node_modules/webgl-raub": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/webgl-raub/-/webgl-raub-4.0.0.tgz", - "integrity": "sha512-fdXxkkieZmplK43QjhFOjroYXOXeac+eXXsIvfirGsnmzJOaZIj7kU4Al4vu2PorWRnvfz6dStSs/9uIQMVp/g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webgl-raub/-/webgl-raub-4.0.2.tgz", + "integrity": "sha512-keUThmq61TbV1bHssdVbLQz28OXlxftxcBjCPeTLRvy7qk/41zWUR07qWa5B98EWwPaXGV8TUBfSxOS0yXTSog==", "hasInstallScript": true, "dependencies": { - "addon-tools-raub": "^7.2.0", - "deps-opengl-raub": "^5.1.0", - "segfault-raub": "^2.1.0" + "addon-tools-raub": "^7.4.0", + "deps-opengl-raub": "^5.1.1", + "segfault-raub": "^2.1.2" }, "engines": { "node": ">=18.16.0", diff --git a/package.json b/package.json index 49c1c9c..bdc2166 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "author": "Luis Blanco ", "name": "3d-core-raub", - "version": "4.0.0", + "version": "4.0.2", "description": "An extensible Node3D core for desktop applications", "license": "MIT", "main": "index.js", @@ -30,7 +30,7 @@ }, "scripts": { "eslint": "eslint .", - "test": "node --test --watch", + "test": "node --test --watch .", "test-ci": "node --test" }, "repository": { @@ -38,21 +38,23 @@ "url": "https://github.com/node-3d/3d-core-raub.git" }, "peerDependencies": { - "three": "^0.157.0" + "three": "^0.152.0" }, "peerDependenciesMeta": { "three": { "optional": true } }, "dependencies": { - "addon-tools-raub": "^7.2.0", - "image-raub": "^4.1.1", - "glfw-raub": "^5.2.0", - "webgl-raub": "^4.0.0" + "addon-tools-raub": "^7.4.0", + "image-raub": "^4.1.3", + "glfw-raub": "^5.2.2", + "webgl-raub": "^4.0.2" }, "devDependencies": { + "@types/node": "^20.8.3", + "@types/three": "0.156.0", "eslint-plugin-node": "^11.1.0", - "eslint": "^8.40.0", + "eslint": "^8.51.0", "three": "0.157.0", - "typescript": "^5.0.4" + "typescript": "^5.2.2" } } diff --git a/test/index.test.js b/test/index.test.js index d490add..3b6eccd 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -165,12 +165,12 @@ describe('Node.js 3D Core', () => { const instance = current.create({ screen }); it('can be created', () => { - assert.strictEqual(instance instanceof inited[c], true); + assert.ok(instance instanceof inited[c]); }); current.props.forEach((prop) => { it(`#${prop} property exposed`, () => { - assert.strictEqual(instance[prop] !== undefined, true); + assert.ok(instance[prop] !== undefined); }); }); @@ -196,7 +196,7 @@ describe('Node.js 3D Core', () => { const instance = current.create(); it('can be created', () => { - assert.strictEqual(instance instanceof inited[c], true); + assert.ok(instance instanceof inited[c]); }); })); });