import TempNode from '../core/TempNode.js';
import { NodeUpdateType } from '../core/constants.js';
import { nodeProxy } from '../tsl/TSLBase.js';
import { CubeTexture } from '../../textures/CubeTexture.js';
import { cubeTexture } from '../accessors/CubeTextureNode.js';
import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js';
import { CubeReflectionMapping, CubeRefractionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from '../../constants.js';
/** @module CubeMapNode **/
const _cache = new WeakMap();
/**
* This node can be used to automatically convert environment maps in the
* equirectangular format into the cube map format.
*
* @augments TempNode
*/
class CubeMapNode extends TempNode {
static get type() {
return 'CubeMapNode';
}
/**
* Constructs a new cube map node.
*
* @param {Node} envNode - The node representing the environment map.
*/
constructor( envNode ) {
super( 'vec3' );
/**
* The node representing the environment map.
*
* @type {Node}
*/
this.envNode = envNode;
/**
* A reference to the internal cube texture.
*
* @private
* @type {CubeTexture}
* @default null
*/
this._cubeTexture = null;
/**
* A reference to the internal cube texture node.
*
* @private
* @type {CubeTextureNode}
*/
this._cubeTextureNode = cubeTexture();
const defaultTexture = new CubeTexture();
defaultTexture.isRenderTargetTexture = true;
/**
* A default cube texture that acts as a placeholder.
* It is used when the conversion from equirectangular to cube
* map has not finished yet for a given texture.
*
* @private
* @type {CubeTexture}
*/
this._defaultTexture = defaultTexture;
/**
* The `updateBeforeType` is set to `NodeUpdateType.RENDER` since the node updates
* the texture once per render in its {@link CubeMapNode#updateBefore} method.
*
* @type {String}
* @default 'render'
*/
this.updateBeforeType = NodeUpdateType.RENDER;
}
updateBefore( frame ) {
const { renderer, material } = frame;
const envNode = this.envNode;
if ( envNode.isTextureNode || envNode.isMaterialReferenceNode ) {
const texture = ( envNode.isTextureNode ) ? envNode.value : material[ envNode.property ];
if ( texture && texture.isTexture ) {
const mapping = texture.mapping;
if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) {
// check for converted cubemap map
if ( _cache.has( texture ) ) {
const cubeMap = _cache.get( texture );
mapTextureMapping( cubeMap, texture.mapping );
this._cubeTexture = cubeMap;
} else {
// create cube map from equirectangular map
const image = texture.image;
if ( isEquirectangularMapReady( image ) ) {
const renderTarget = new CubeRenderTarget( image.height );
renderTarget.fromEquirectangularTexture( renderer, texture );
mapTextureMapping( renderTarget.texture, texture.mapping );
this._cubeTexture = renderTarget.texture;
_cache.set( texture, renderTarget.texture );
texture.addEventListener( 'dispose', onTextureDispose );
} else {
// default cube texture as fallback when equirectangular texture is not yet loaded
this._cubeTexture = this._defaultTexture;
}
}
//
this._cubeTextureNode.value = this._cubeTexture;
} else {
// envNode already refers to a cube map
this._cubeTextureNode = this.envNode;
}
}
}
}
setup( builder ) {
this.updateBefore( builder );
return this._cubeTextureNode;
}
}
export default CubeMapNode;
/**
* Returns true if the given equirectangular image has been fully loaded
* and is ready for further processing.
*
* @private
* @param {Image} image - The equirectangular image to check.
* @return {Boolean} Whether the image is ready or not.
*/
function isEquirectangularMapReady( image ) {
if ( image === null || image === undefined ) return false;
return image.height > 0;
}
/**
* This function is executed when `dispose()` is called on the equirectangular
* texture. In this case, the generated cube map with its render target
* is deleted as well.
*
* @private
* @param {Object} event - The event object.
*/
function onTextureDispose( event ) {
const texture = event.target;
texture.removeEventListener( 'dispose', onTextureDispose );
const renderTarget = _cache.get( texture );
if ( renderTarget !== undefined ) {
_cache.delete( texture );
renderTarget.dispose();
}
}
/**
* This function makes sure the generated cube map uses the correct
* texture mapping that corresponds to the equirectangular original.
*
* @private
* @param {Texture} texture - The cube texture.
* @param {Number} mapping - The original texture mapping.
*/
function mapTextureMapping( texture, mapping ) {
if ( mapping === EquirectangularReflectionMapping ) {
texture.mapping = CubeReflectionMapping;
} else if ( mapping === EquirectangularRefractionMapping ) {
texture.mapping = CubeRefractionMapping;
}
}
/**
* TSL function for creating a cube map node.
*
* @function
* @param {Node} envNode - The node representing the environment map.
* @returns {CubeMapNode}
*/
export const cubeMapNode = /*@__PURE__*/ nodeProxy( CubeMapNode );