-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Copy pathmaterial.js
325 lines (279 loc) · 10.1 KB
/
material.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/* global HTMLCanvasElement, HTMLVideoElement */
import * as THREE from 'three';
import * as srcLoader from './src-loader.js';
import debug from './debug.js';
var warn = debug('utils:material:warn');
var COLOR_MAPS = new Set([
'emissiveMap',
'envMap',
'map',
'specularMap'
]);
/**
* Set texture properties such as repeat and offset.
*
* @param {THREE.Texture} texture - a Texture instance.
* @param {object} data - With keys like `repeat`.
*/
export function setTextureProperties (texture, data) {
var offset = data.offset || {x: 0, y: 0};
var repeat = data.repeat || {x: 1, y: 1};
var npot = data.npot || false;
var anisotropy = data.anisotropy || THREE.Texture.DEFAULT_ANISOTROPY;
var wrapS = texture.wrapS;
var wrapT = texture.wrapT;
var magFilter = texture.magFilter;
var minFilter = texture.minFilter;
// To support NPOT textures, wrap must be ClampToEdge (not Repeat),
// and filters must not use mipmaps (i.e. Nearest or Linear).
if (npot) {
wrapS = THREE.ClampToEdgeWrapping;
wrapT = THREE.ClampToEdgeWrapping;
magFilter = THREE.LinearFilter;
minFilter = THREE.LinearFilter;
}
// Set wrap mode to repeat only if repeat isn't 1/1. Power-of-two is required to repeat.
if (repeat.x !== 1 || repeat.y !== 1) {
wrapS = THREE.RepeatWrapping;
wrapT = THREE.RepeatWrapping;
}
// Apply texture properties
texture.offset.set(offset.x, offset.y);
texture.repeat.set(repeat.x, repeat.y);
if (texture.wrapS !== wrapS || texture.wrapT !== wrapT ||
texture.magFilter !== magFilter || texture.minFilter !== minFilter ||
texture.anisotropy !== anisotropy) {
texture.wrapS = wrapS;
texture.wrapT = wrapT;
texture.magFilter = magFilter;
texture.minFilter = minFilter;
texture.anisotropy = anisotropy;
texture.needsUpdate = true;
}
}
/**
* Update `material` texture property (usually but not always `map`)
* from `data` property (usually but not always `src`).
*
* @param {string} materialName
* @param {string} dataName
* @param {object} shader - A-Frame shader instance.
* @param {object} data
*/
export function updateMapMaterialFromData (materialName, dataName, shader, data) {
var el = shader.el;
var material = shader.material;
var rendererSystem = el.sceneEl.systems.renderer;
var src = data[dataName];
// Because a single material / shader may have multiple textures,
// we need to remember the source value for this data property
// to avoid redundant operations which can be expensive otherwise
// (e.g. video texture loads).
if (!shader.materialSrcs) { shader.materialSrcs = {}; }
if (!src) {
// Forget the prior material src.
delete shader.materialSrcs[materialName];
// Remove the texture from the material.
setMap(null);
return;
}
// If material src hasn't changed, and we already have a texture,
// just update properties, but don't reload the texture.
if (src === shader.materialSrcs[materialName] &&
material[materialName]) {
setTextureProperties(material[materialName], data);
return;
}
// Remember the new src for this texture (there may be multiple).
shader.materialSrcs[materialName] = src;
// If the new material src is already a texture, just use it.
if (src instanceof THREE.Texture) { setMap(src); } else {
// Load texture source for the new material src.
// (And check if we should still use it once available in callback.)
el.sceneEl.systems.material.loadTextureSource(src, updateTexture);
}
function updateTexture (source) {
// If the source has been changed, don't use loaded texture.
if (shader.materialSrcs[materialName] !== src) { return; }
var texture = material[materialName];
// Handle removal or texture type change
if (texture && (source === null || !isCompatibleTexture(texture, source))) {
texture = null;
}
// Create texture if needed
if (!texture && source) {
texture = createCompatibleTexture(source);
}
// Update texture source and properties
if (texture) {
if (texture.source !== source) {
texture.source = source;
texture.needsUpdate = true;
}
if (COLOR_MAPS.has(materialName)) {
rendererSystem.applyColorCorrection(texture);
}
setTextureProperties(texture, data);
}
// Set map property on the material
setMap(texture);
}
function setMap (texture) {
// Nothing to do if texture is the same
if (material[materialName] === texture) {
return;
}
// Dispose old texture if present
if (material[materialName]) {
material[materialName].dispose();
}
material[materialName] = texture;
material.needsUpdate = true;
handleTextureEvents(el, texture);
}
}
/**
* Update `material.map` given `data.src`. For standard and flat shaders.
*
* @param {object} shader - A-Frame shader instance.
* @param {object} data
*/
export function updateMap (shader, data) {
return updateMapMaterialFromData('map', 'src', shader, data);
}
/**
* Updates the material's maps which give the illusion of extra geometry.
*
* @param {string} longType - The friendly name of the map from the component e.g. ambientOcclusionMap becomes aoMap in THREE.js
* @param {object} shader - A-Frame shader instance
* @param {object} data
*/
export function updateDistortionMap (longType, shader, data) {
var shortType = longType;
if (longType === 'ambientOcclusion') { shortType = 'ao'; }
var info = {};
info.src = data[longType + 'Map'];
// Pass through the repeat and offset to be handled by the material loader.
info.offset = data[longType + 'TextureOffset'];
info.repeat = data[longType + 'TextureRepeat'];
info.wrap = data[longType + 'TextureWrap'];
return updateMapMaterialFromData(shortType + 'Map', 'src', shader, info);
}
// Cache env map results as promises
var envMapPromises = {};
/**
* Updates the material's environment map providing reflections or refractions.
*
* @param {object} shader - A-Frame shader instance
* @param {object} data
*/
export function updateEnvMap (shader, data) {
var material = shader.material;
var el = shader.el;
var materialName = 'envMap';
var src = data.envMap;
var sphericalEnvMap = data.sphericalEnvMap;
var refract = data.refract;
if (sphericalEnvMap) {
src = sphericalEnvMap;
warn('`sphericalEnvMap` property is deprecated, using spherical map as equirectangular map instead. ' +
'Use `envMap` property with a CubeMap or Equirectangular image instead.');
}
if (!shader.materialSrcs) { shader.materialSrcs = {}; }
// EnvMap has been removed
if (!src) {
// Forget the prior material src.
delete shader.materialSrcs[materialName];
material.envMap = null;
material.needsUpdate = true;
return;
}
// Remember the new src for this env map.
shader.materialSrcs[materialName] = src;
// Env map is already loading. Wait on promise.
if (envMapPromises[src]) {
envMapPromises[src].then(checkSetMap);
return;
}
// First time loading this env map.
envMapPromises[src] = new Promise(function (resolve) {
srcLoader.validateEnvMapSrc(src, function loadCubeMap (srcs) {
el.sceneEl.systems.material.loadCubeMapTexture(srcs, function (texture) {
texture.mapping = refract ? THREE.CubeRefractionMapping : THREE.CubeReflectionMapping;
checkSetMap(texture);
resolve(texture);
});
}, function loadEquirectMap (src) {
el.sceneEl.systems.material.loadTexture(src, {src: src}, function (texture) {
texture.mapping = refract ? THREE.EquirectangularRefractionMapping : THREE.EquirectangularReflectionMapping;
checkSetMap(texture);
resolve(texture);
});
});
});
function checkSetMap (texture) {
if (shader.materialSrcs[materialName] !== src) { return; }
material.envMap = texture;
material.needsUpdate = true;
handleTextureEvents(el, texture);
}
}
/**
* Emit event on entities on texture-related events.
*
* @param {Element} el - Entity.
* @param {object} texture - three.js Texture.
*/
export function handleTextureEvents (el, texture) {
if (!texture) { return; }
el.emit('materialtextureloaded', {src: texture.image, texture: texture});
// Video events.
if (!texture.image || texture.image.tagName !== 'VIDEO') { return; }
texture.image.addEventListener('loadeddata', emitVideoTextureLoadedDataAll);
texture.image.addEventListener('ended', emitVideoTextureEndedAll);
function emitVideoTextureLoadedDataAll () {
el.emit('materialvideoloadeddata', {src: texture.image, texture: texture});
}
function emitVideoTextureEndedAll () {
// Works for non-looping videos only.
el.emit('materialvideoended', {src: texture.image, texture: texture});
}
// Video source can outlive texture, so cleanup event listeners when texture is disposed
texture.addEventListener('dispose', function cleanupListeners () {
texture.image.removeEventListener('loadeddata', emitVideoTextureLoadedDataAll);
texture.image.removeEventListener('ended', emitVideoTextureEndedAll);
});
}
/**
* Checks if a given texture type is compatible with a given source.
*
* @param {THREE.Texture} texture - The texture to check compatibility with
* @param {THREE.Source} source - The source to check compatibility with
* @returns {boolean} True if the texture is compatible with the source, false otherwise
*/
export function isCompatibleTexture (texture, source) {
if (texture.source !== source) {
return false;
}
if (source.data instanceof HTMLCanvasElement) {
return texture.isCanvasTexture;
}
if (source.data instanceof HTMLVideoElement) {
return texture.isVideoTexture;
}
return texture.isTexture && !texture.isCanvasTexture && !texture.isVideoTexture;
}
export function createCompatibleTexture (source) {
var texture;
if (source.data instanceof HTMLCanvasElement) {
texture = new THREE.CanvasTexture();
} else if (source.data instanceof HTMLVideoElement) {
// Pass underlying video to constructor to ensure requestVideoFrameCallback is setup
texture = new THREE.VideoTexture(source.data);
} else {
texture = new THREE.Texture();
}
texture.source = source;
texture.needsUpdate = true;
return texture;
}