three
Version:
JavaScript 3D library
684 lines (538 loc) • 16 kB
JavaScript
import { EventDispatcher } from '../core/EventDispatcher.js';
import {
MirroredRepeatWrapping,
ClampToEdgeWrapping,
RepeatWrapping,
UnsignedByteType,
RGBAFormat,
LinearMipmapLinearFilter,
LinearFilter,
UVMapping,
NoColorSpace,
} from '../constants.js';
import { generateUUID } from '../math/MathUtils.js';
import { Vector2 } from '../math/Vector2.js';
import { Matrix3 } from '../math/Matrix3.js';
import { Source } from './Source.js';
let _textureId = 0;
/**
* Base class for all textures.
*
* Note: After the initial use of a texture, its dimensions, format, and type
* cannot be changed. Instead, call {@link Texture#dispose} on the texture and instantiate a new one.
*
* @augments EventDispatcher
*/
class Texture extends EventDispatcher {
/**
* Constructs a new texture.
*
* @param {?Object} [image=Texture.DEFAULT_IMAGE] - The image holding the texture data.
* @param {number} [mapping=Texture.DEFAULT_MAPPING] - The texture mapping.
* @param {number} [wrapS=ClampToEdgeWrapping] - The wrapS value.
* @param {number} [wrapT=ClampToEdgeWrapping] - The wrapT value.
* @param {number} [magFilter=LinearFilter] - The mag filter value.
* @param {number} [minFilter=LinearMipmapLinearFilter] - The min filter value.
* @param {number} [format=RGBAFormat] - The texture format.
* @param {number} [type=UnsignedByteType] - The texture type.
* @param {number} [anisotropy=Texture.DEFAULT_ANISOTROPY] - The anisotropy value.
* @param {string} [colorSpace=NoColorSpace] - The color space.
*/
constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = Texture.DEFAULT_ANISOTROPY, colorSpace = NoColorSpace ) {
super();
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isTexture = true;
/**
* The ID of the texture.
*
* @name Texture#id
* @type {number}
* @readonly
*/
Object.defineProperty( this, 'id', { value: _textureId ++ } );
/**
* The UUID of the material.
*
* @type {string}
* @readonly
*/
this.uuid = generateUUID();
/**
* The name of the material.
*
* @type {string}
*/
this.name = '';
/**
* The data definition of a texture. A reference to the data source can be
* shared across textures. This is often useful in context of spritesheets
* where multiple textures render the same data but with different texture
* transformations.
*
* @type {Source}
*/
this.source = new Source( image );
/**
* An array holding user-defined mipmaps.
*
* @type {Array<Object>}
*/
this.mipmaps = [];
/**
* How the texture is applied to the object. The value `UVMapping`
* is the default, where texture or uv coordinates are used to apply the map.
*
* @type {(UVMapping|CubeReflectionMapping|CubeRefractionMapping|EquirectangularReflectionMapping|EquirectangularRefractionMapping|CubeUVReflectionMapping)}
* @default UVMapping
*/
this.mapping = mapping;
/**
* Lets you select the uv attribute to map the texture to. `0` for `uv`,
* `1` for `uv1`, `2` for `uv2` and `3` for `uv3`.
*
* @type {number}
* @default 0
*/
this.channel = 0;
/**
* This defines how the texture is wrapped horizontally and corresponds to
* *U* in UV mapping.
*
* @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)}
* @default ClampToEdgeWrapping
*/
this.wrapS = wrapS;
/**
* This defines how the texture is wrapped horizontally and corresponds to
* *V* in UV mapping.
*
* @type {(RepeatWrapping|ClampToEdgeWrapping|MirroredRepeatWrapping)}
* @default ClampToEdgeWrapping
*/
this.wrapT = wrapT;
/**
* How the texture is sampled when a texel covers more than one pixel.
*
* @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)}
* @default LinearFilter
*/
this.magFilter = magFilter;
/**
* How the texture is sampled when a texel covers less than one pixel.
*
* @type {(NearestFilter|NearestMipmapNearestFilter|NearestMipmapLinearFilter|LinearFilter|LinearMipmapNearestFilter|LinearMipmapLinearFilter)}
* @default LinearMipmapLinearFilter
*/
this.minFilter = minFilter;
/**
* The number of samples taken along the axis through the pixel that has the
* highest density of texels. By default, this value is `1`. A higher value
* gives a less blurry result than a basic mipmap, at the cost of more
* texture samples being used.
*
* @type {number}
* @default 0
*/
this.anisotropy = anisotropy;
/**
* The format of the texture.
*
* @type {number}
* @default RGBAFormat
*/
this.format = format;
/**
* The default internal format is derived from {@link Texture#format} and {@link Texture#type} and
* defines how the texture data is going to be stored on the GPU.
*
* This property allows to overwrite the default format.
*
* @type {?string}
* @default null
*/
this.internalFormat = null;
/**
* The data type of the texture.
*
* @type {number}
* @default UnsignedByteType
*/
this.type = type;
/**
* How much a single repetition of the texture is offset from the beginning,
* in each direction U and V. Typical range is `0.0` to `1.0`.
*
* @type {Vector2}
* @default (0,0)
*/
this.offset = new Vector2( 0, 0 );
/**
* How many times the texture is repeated across the surface, in each
* direction U and V. If repeat is set greater than `1` in either direction,
* the corresponding wrap parameter should also be set to `RepeatWrapping`
* or `MirroredRepeatWrapping` to achieve the desired tiling effect.
*
* @type {Vector2}
* @default (1,1)
*/
this.repeat = new Vector2( 1, 1 );
/**
* The point around which rotation occurs. A value of `(0.5, 0.5)` corresponds
* to the center of the texture. Default is `(0, 0)`, the lower left.
*
* @type {Vector2}
* @default (0,0)
*/
this.center = new Vector2( 0, 0 );
/**
* How much the texture is rotated around the center point, in radians.
* Positive values are counter-clockwise.
*
* @type {number}
* @default 0
*/
this.rotation = 0;
/**
* Whether to update the texture's uv-transformation {@link Texture#matrix}
* from the properties {@link Texture#offset}, {@link Texture#repeat},
* {@link Texture#rotation}, and {@link Texture#center}.
*
* Set this to `false` if you are specifying the uv-transform matrix directly.
*
* @type {boolean}
* @default true
*/
this.matrixAutoUpdate = true;
/**
* The uv-transformation matrix of the texture.
*
* @type {Matrix3}
*/
this.matrix = new Matrix3();
/**
* Whether to generate mipmaps (if possible) for a texture.
*
* Set this to `false` if you are creating mipmaps manually.
*
* @type {boolean}
* @default true
*/
this.generateMipmaps = true;
/**
* If set to `true`, the alpha channel, if present, is multiplied into the
* color channels when the texture is uploaded to the GPU.
*
* Note that this property has no effect when using `ImageBitmap`. You need to
* configure premultiply alpha on bitmap creation instead.
*
* @type {boolean}
* @default false
*/
this.premultiplyAlpha = false;
/**
* If set to `true`, the texture is flipped along the vertical axis when
* uploaded to the GPU.
*
* Note that this property has no effect when using `ImageBitmap`. You need to
* configure the flip on bitmap creation instead.
*
* @type {boolean}
* @default true
*/
this.flipY = true;
/**
* Specifies the alignment requirements for the start of each pixel row in memory.
* The allowable values are `1` (byte-alignment), `2` (rows aligned to even-numbered bytes),
* `4` (word-alignment), and `8` (rows start on double-word boundaries).
*
* @type {number}
* @default 4
*/
this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
/**
* Textures containing color data should be annotated with `SRGBColorSpace` or `LinearSRGBColorSpace`.
*
* @type {string}
* @default NoColorSpace
*/
this.colorSpace = colorSpace;
/**
* An object that can be used to store custom data about the texture. It
* should not hold references to functions as these will not be cloned.
*
* @type {Object}
*/
this.userData = {};
/**
* This starts at `0` and counts how many times {@link Texture#needsUpdate} is set to `true`.
*
* @type {number}
* @readonly
* @default 0
*/
this.version = 0;
/**
* A callback function, called when the texture is updated (e.g., when
* {@link Texture#needsUpdate} has been set to true and then the texture is used).
*
* @type {?Function}
* @default null
*/
this.onUpdate = null;
/**
* An optional back reference to the textures render target.
*
* @type {?(RenderTarget|WebGLRenderTarget)}
* @default null
*/
this.renderTarget = null;
/**
* Indicates whether a texture belongs to a render target or not.
*
* @type {boolean}
* @readonly
* @default false
*/
this.isRenderTargetTexture = false;
/**
* Indicates whether this texture should be processed by `PMREMGenerator` or not
* (only relevant for render target textures).
*
* @type {number}
* @readonly
* @default 0
*/
this.pmremVersion = 0;
}
/**
* The image object holding the texture data.
*
* @type {?Object}
*/
get image() {
return this.source.data;
}
set image( value = null ) {
this.source.data = value;
}
/**
* Updates the texture transformation matrix from the from the properties {@link Texture#offset},
* {@link Texture#repeat}, {@link Texture#rotation}, and {@link Texture#center}.
*/
updateMatrix() {
this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y );
}
/**
* Returns a new texture with copied values from this instance.
*
* @return {Texture} A clone of this instance.
*/
clone() {
return new this.constructor().copy( this );
}
/**
* Copies the values of the given texture to this instance.
*
* @param {Texture} source - The texture to copy.
* @return {Texture} A reference to this instance.
*/
copy( source ) {
this.name = source.name;
this.source = source.source;
this.mipmaps = source.mipmaps.slice( 0 );
this.mapping = source.mapping;
this.channel = source.channel;
this.wrapS = source.wrapS;
this.wrapT = source.wrapT;
this.magFilter = source.magFilter;
this.minFilter = source.minFilter;
this.anisotropy = source.anisotropy;
this.format = source.format;
this.internalFormat = source.internalFormat;
this.type = source.type;
this.offset.copy( source.offset );
this.repeat.copy( source.repeat );
this.center.copy( source.center );
this.rotation = source.rotation;
this.matrixAutoUpdate = source.matrixAutoUpdate;
this.matrix.copy( source.matrix );
this.generateMipmaps = source.generateMipmaps;
this.premultiplyAlpha = source.premultiplyAlpha;
this.flipY = source.flipY;
this.unpackAlignment = source.unpackAlignment;
this.colorSpace = source.colorSpace;
this.renderTarget = source.renderTarget;
this.isRenderTargetTexture = source.isRenderTargetTexture;
this.userData = JSON.parse( JSON.stringify( source.userData ) );
this.needsUpdate = true;
return this;
}
/**
* Serializes the texture into JSON.
*
* @param {?(Object|string)} meta - An optional value holding meta information about the serialization.
* @return {Object} A JSON object representing the serialized texture.
* @see {@link ObjectLoader#parse}
*/
toJSON( meta ) {
const isRootObject = ( meta === undefined || typeof meta === 'string' );
if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) {
return meta.textures[ this.uuid ];
}
const output = {
metadata: {
version: 4.6,
type: 'Texture',
generator: 'Texture.toJSON'
},
uuid: this.uuid,
name: this.name,
image: this.source.toJSON( meta ).uuid,
mapping: this.mapping,
channel: this.channel,
repeat: [ this.repeat.x, this.repeat.y ],
offset: [ this.offset.x, this.offset.y ],
center: [ this.center.x, this.center.y ],
rotation: this.rotation,
wrap: [ this.wrapS, this.wrapT ],
format: this.format,
internalFormat: this.internalFormat,
type: this.type,
colorSpace: this.colorSpace,
minFilter: this.minFilter,
magFilter: this.magFilter,
anisotropy: this.anisotropy,
flipY: this.flipY,
generateMipmaps: this.generateMipmaps,
premultiplyAlpha: this.premultiplyAlpha,
unpackAlignment: this.unpackAlignment
};
if ( Object.keys( this.userData ).length > 0 ) output.userData = this.userData;
if ( ! isRootObject ) {
meta.textures[ this.uuid ] = output;
}
return output;
}
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever this instance is no longer used in your app.
*
* @fires Texture#dispose
*/
dispose() {
/**
* Fires when the texture has been disposed of.
*
* @event Texture#dispose
* @type {Object}
*/
this.dispatchEvent( { type: 'dispose' } );
}
/**
* Transforms the given uv vector with the textures uv transformation matrix.
*
* @param {Vector2} uv - The uv vector.
* @return {Vector2} The transformed uv vector.
*/
transformUv( uv ) {
if ( this.mapping !== UVMapping ) return uv;
uv.applyMatrix3( this.matrix );
if ( uv.x < 0 || uv.x > 1 ) {
switch ( this.wrapS ) {
case RepeatWrapping:
uv.x = uv.x - Math.floor( uv.x );
break;
case ClampToEdgeWrapping:
uv.x = uv.x < 0 ? 0 : 1;
break;
case MirroredRepeatWrapping:
if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) {
uv.x = Math.ceil( uv.x ) - uv.x;
} else {
uv.x = uv.x - Math.floor( uv.x );
}
break;
}
}
if ( uv.y < 0 || uv.y > 1 ) {
switch ( this.wrapT ) {
case RepeatWrapping:
uv.y = uv.y - Math.floor( uv.y );
break;
case ClampToEdgeWrapping:
uv.y = uv.y < 0 ? 0 : 1;
break;
case MirroredRepeatWrapping:
if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) {
uv.y = Math.ceil( uv.y ) - uv.y;
} else {
uv.y = uv.y - Math.floor( uv.y );
}
break;
}
}
if ( this.flipY ) {
uv.y = 1 - uv.y;
}
return uv;
}
/**
* Setting this property to `true` indicates the engine the texture
* must be updated in the next render. This triggers a texture upload
* to the GPU and ensures correct texture parameter configuration.
*
* @type {boolean}
* @default false
* @param {boolean} value
*/
set needsUpdate( value ) {
if ( value === true ) {
this.version ++;
this.source.needsUpdate = true;
}
}
/**
* Setting this property to `true` indicates the engine the PMREM
* must be regenerated.
*
* @type {boolean}
* @default false
* @param {boolean} value
*/
set needsPMREMUpdate( value ) {
if ( value === true ) {
this.pmremVersion ++;
}
}
}
/**
* The default image for all textures.
*
* @static
* @type {?Image}
* @default null
*/
Texture.DEFAULT_IMAGE = null;
/**
* The default mapping for all textures.
*
* @static
* @type {number}
* @default UVMapping
*/
Texture.DEFAULT_MAPPING = UVMapping;
/**
* The default anisotropy value for all textures.
*
* @static
* @type {number}
* @default 1
*/
Texture.DEFAULT_ANISOTROPY = 1;
export { Texture };