@combeenation/3d-viewer 14.1.0 → 15.1.0-beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib-cjs/buildinfo.json +1 -1
- package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
- package/dist/lib-cjs/index.d.ts +1 -1
- package/dist/lib-cjs/index.js +1 -1
- package/dist/lib-cjs/index.js.map +1 -1
- package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js +0 -16
- package/dist/lib-cjs/internal/cbn-custom-babylon-loader-plugin.js.map +1 -1
- package/dist/lib-cjs/internal/cloning-helper.d.ts +4 -4
- package/dist/lib-cjs/internal/cloning-helper.js +22 -7
- package/dist/lib-cjs/internal/cloning-helper.js.map +1 -1
- package/dist/lib-cjs/internal/paintable-helper.js +2 -54
- package/dist/lib-cjs/internal/paintable-helper.js.map +1 -1
- package/dist/lib-cjs/internal/svg-helper.d.ts +4 -0
- package/dist/lib-cjs/internal/svg-helper.js +67 -0
- package/dist/lib-cjs/internal/svg-helper.js.map +1 -0
- package/dist/lib-cjs/internal/tags-helper.d.ts +1 -1
- package/dist/lib-cjs/internal/tags-helper.js +10 -8
- package/dist/lib-cjs/internal/tags-helper.js.map +1 -1
- package/dist/lib-cjs/internal/texture-parameter-helper.d.ts +37 -0
- package/dist/lib-cjs/internal/texture-parameter-helper.js +287 -0
- package/dist/lib-cjs/internal/texture-parameter-helper.js.map +1 -0
- package/dist/lib-cjs/manager/material-manager.d.ts +22 -1
- package/dist/lib-cjs/manager/material-manager.js +68 -0
- package/dist/lib-cjs/manager/material-manager.js.map +1 -1
- package/dist/lib-cjs/manager/model-manager.d.ts +4 -2
- package/dist/lib-cjs/manager/model-manager.js +7 -1
- package/dist/lib-cjs/manager/model-manager.js.map +1 -1
- package/dist/lib-cjs/manager/parameter-manager.d.ts +40 -8
- package/dist/lib-cjs/manager/parameter-manager.js +88 -11
- package/dist/lib-cjs/manager/parameter-manager.js.map +1 -1
- package/dist/lib-cjs/viewer-error.d.ts +3 -0
- package/dist/lib-cjs/viewer-error.js +3 -0
- package/dist/lib-cjs/viewer-error.js.map +1 -1
- package/dist/lib-cjs/viewer.js.map +1 -1
- package/package.json +13 -10
- package/src/index.ts +1 -1
- package/src/internal/cbn-custom-babylon-loader-plugin.ts +2 -21
- package/src/internal/cloning-helper.ts +29 -5
- package/src/internal/paintable-helper.ts +2 -54
- package/src/internal/svg-helper.ts +52 -0
- package/src/internal/tags-helper.ts +9 -7
- package/src/internal/texture-parameter-helper.ts +353 -0
- package/src/manager/material-manager.ts +62 -0
- package/src/manager/model-manager.ts +11 -5
- package/src/manager/parameter-manager.ts +116 -16
- package/src/viewer-error.ts +3 -0
- package/src/viewer.ts +1 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces all supported image & font URLs in the given SVG with their base64 representation.
|
|
3
|
+
*/
|
|
4
|
+
export async function embedAssets(svgSrc: string): Promise<string> {
|
|
5
|
+
const _imageExtensions = ['png', 'gif', 'jpg', 'jpeg', 'svg', 'bmp'];
|
|
6
|
+
const _fontExtensions = ['woff2', 'woff', 'ttf', 'otf'];
|
|
7
|
+
const _assetExtensions = [..._imageExtensions, ..._fontExtensions];
|
|
8
|
+
// Regex copied from https://stackoverflow.com/a/8943487/1273551, not "stress tested"...
|
|
9
|
+
const urlRegex = /(\bhttps?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
|
|
10
|
+
const allUrls = svgSrc.match(urlRegex) as string[];
|
|
11
|
+
|
|
12
|
+
const assetUrls = allUrls.filter(url => {
|
|
13
|
+
const indexParam = url.indexOf('?');
|
|
14
|
+
// remove url parameter to recognize extension
|
|
15
|
+
if (indexParam > -1) {
|
|
16
|
+
url = url.substring(0, indexParam);
|
|
17
|
+
}
|
|
18
|
+
return _assetExtensions.some(extension => url.toLowerCase().endsWith(`.${extension}`));
|
|
19
|
+
});
|
|
20
|
+
const assetBase64Fetcher = assetUrls.map(_fetchBase64AssetUrl);
|
|
21
|
+
const assetFetcherResults = await Promise.all(assetBase64Fetcher);
|
|
22
|
+
return assetFetcherResults.reduce((svgSrc, x) => svgSrc.replace(x.url, x.base64), svgSrc);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fetch asset (image or font) and convert it to base64 string representation.
|
|
27
|
+
*/
|
|
28
|
+
async function _fetchBase64AssetUrl(assetUrl: string): Promise<{ url: string; base64: string }> {
|
|
29
|
+
// TODO WTT: Cache known base64 representation and only fetch/convert when not already known.
|
|
30
|
+
// Usually the fetch shouldn't hit the network but the browser cache since the SVG was already drawn..
|
|
31
|
+
const resp = await fetch(assetUrl);
|
|
32
|
+
const blob = await resp.blob();
|
|
33
|
+
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const reader = new FileReader();
|
|
36
|
+
reader.onloadend = (event): void => {
|
|
37
|
+
const target = event.target;
|
|
38
|
+
if (!target) {
|
|
39
|
+
return reject(`Asset with URL "${assetUrl}" could not be loaded.`);
|
|
40
|
+
}
|
|
41
|
+
const result = target.result;
|
|
42
|
+
if (!result) {
|
|
43
|
+
return reject(`Asset with URL "${assetUrl}" returned an empty result.`);
|
|
44
|
+
}
|
|
45
|
+
resolve({
|
|
46
|
+
url: assetUrl,
|
|
47
|
+
base64: result.toString() as string,
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
reader.readAsDataURL(blob);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -19,18 +19,13 @@ export function getTags(object: TagTarget): string[] {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function setTags(object: TagTarget, tags: string[]): void {
|
|
22
|
+
deleteAllTags(object);
|
|
22
23
|
const tagsString = tags.join(' ');
|
|
23
24
|
Tags.AddTagsTo(object, tagsString);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export function deleteAllTags(object: TagTarget): void {
|
|
27
|
-
const curTags = Tags.GetTags(object);
|
|
28
|
-
if (curTags) {
|
|
29
|
-
Tags.RemoveTagsFrom(object, curTags);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
27
|
export function setTagsAsString(object: TagTarget, tagsString: string): void {
|
|
28
|
+
deleteAllTags(object);
|
|
34
29
|
Tags.AddTagsTo(object, tagsString);
|
|
35
30
|
}
|
|
36
31
|
|
|
@@ -39,3 +34,10 @@ export function cloneTags(sourceObject: TagTarget, destinationObject: TagTarget)
|
|
|
39
34
|
Tags.AddTagsTo(destinationObject, Tags.GetTags(sourceObject, true));
|
|
40
35
|
}
|
|
41
36
|
}
|
|
37
|
+
|
|
38
|
+
export function deleteAllTags(object: TagTarget): void {
|
|
39
|
+
const curTags = Tags.GetTags(object);
|
|
40
|
+
if (curTags) {
|
|
41
|
+
Tags.RemoveTagsFrom(object, curTags);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { BaseTexture, Material, PBRMaterial, Scene, Texture, ViewerError, ViewerErrorIds } from '..';
|
|
2
|
+
import { BuiltInParameter, ParameterManager } from '../manager/parameter-manager';
|
|
3
|
+
import { embedAssets } from './svg-helper';
|
|
4
|
+
import isSvg from 'is-svg';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Texture parameters are a combination of the channel (e.g. albedo, bump, AO) and the "sub" parameter (e.g. uScale).
|
|
8
|
+
* Each parameter is available in each texture, that's why these textures parameter are defined as dotted path
|
|
9
|
+
* `albedoTexture.uScale`
|
|
10
|
+
* - channel: albedoTexture
|
|
11
|
+
* - parameter: uScale
|
|
12
|
+
* The consumer doesn't have to know this as there is `BuiltInParameters.createTextureParameter(channel, parameter)`,
|
|
13
|
+
* which composes this string internally
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const BuiltInTextureParameter = {
|
|
17
|
+
image: 'image',
|
|
18
|
+
uOffset: 'uOffset',
|
|
19
|
+
vOffset: 'vOffset',
|
|
20
|
+
uScale: 'uScale',
|
|
21
|
+
vScale: 'vScale',
|
|
22
|
+
uAng: 'uAng',
|
|
23
|
+
vAng: 'vAng',
|
|
24
|
+
wAng: 'wAng',
|
|
25
|
+
clampU: 'clampU',
|
|
26
|
+
clampV: 'clampV',
|
|
27
|
+
uvSet: 'uvSet',
|
|
28
|
+
};
|
|
29
|
+
export type BuiltInTextureParameterKeys = keyof typeof BuiltInTextureParameter;
|
|
30
|
+
|
|
31
|
+
export const ParameterTextureChannels = {
|
|
32
|
+
albedoTexture: 'albedoTexture',
|
|
33
|
+
metallicRoughnessTexture: 'metallicRoughnessTexture',
|
|
34
|
+
bumpTexture: 'bumpTexture',
|
|
35
|
+
emissiveTexture: 'emissiveTexture',
|
|
36
|
+
opacityTexture: 'opacityTexture',
|
|
37
|
+
ambientTexture: 'ambientTexture',
|
|
38
|
+
lightmapTexture: 'lightmapTexture',
|
|
39
|
+
detailmapTexture: 'detailmapTexture',
|
|
40
|
+
};
|
|
41
|
+
export type ParameterTextureChannelsKeys = keyof typeof ParameterTextureChannels;
|
|
42
|
+
|
|
43
|
+
export function createBuiltInTextureParameter(parameterManager: ParameterManager, scene: Scene): void {
|
|
44
|
+
// create parameter observer for each channel
|
|
45
|
+
(Object.values(ParameterTextureChannels) as ParameterTextureChannelsKeys[]).forEach(channel => {
|
|
46
|
+
parameterManager.setParameterObserver(
|
|
47
|
+
`${channel}.${BuiltInTextureParameter.image}`,
|
|
48
|
+
async ({ newValue, materials }) => {
|
|
49
|
+
const image = ParameterManager.parseString(newValue);
|
|
50
|
+
let url = image;
|
|
51
|
+
|
|
52
|
+
const imageIsSvg = isSvg(image);
|
|
53
|
+
if (imageIsSvg) {
|
|
54
|
+
// NOTE: image can also be provided as "svg", which is more or less the successor of the "paintable"
|
|
55
|
+
// parameter. It misses the UV parameters, but we now have dedicated parameters for this.
|
|
56
|
+
// We used "Dynamic Textures" for paintables because we didn't know better I guess?
|
|
57
|
+
// However the rescaling functionality of "Dynamic Textures" is not required and therefore it can be
|
|
58
|
+
// implemented as a plain "Texture"
|
|
59
|
+
const svgWithAssetsEmbedded = await embedAssets(image);
|
|
60
|
+
// convert into a base64 string, as this can be directly interpreted as "url", at least for Babylon.js
|
|
61
|
+
url = 'data:image/svg+xml;base64,' + btoa(svgWithAssetsEmbedded);
|
|
62
|
+
} else if (image.includes('<svg') && image.includes('</svg>')) {
|
|
63
|
+
// seems like the user tried to use a SVG string, as <svg> tags are used
|
|
64
|
+
// inform the user that this is not a valid SVG string
|
|
65
|
+
throw new ViewerError({
|
|
66
|
+
id: ViewerErrorIds.InvalidParameterValue,
|
|
67
|
+
message: `Invalid value for parameter "image" given:\nsource string is no valid SVG string\nGiven value: ${image}`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const material of materials) {
|
|
72
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.image);
|
|
73
|
+
const texture = _getTextureFromParameterChannel(pbrMaterial, channel);
|
|
74
|
+
|
|
75
|
+
if (texture) {
|
|
76
|
+
// create a clone of the texture, in this way we can load the image before assigning it to the material
|
|
77
|
+
// channel
|
|
78
|
+
const clonedTexture = texture.clone();
|
|
79
|
+
// update texture and await loading time right away
|
|
80
|
+
await new Promise<void>(resolve => clonedTexture.updateURL(url, undefined, resolve));
|
|
81
|
+
|
|
82
|
+
_assignTextureParameterChannel(clonedTexture, pbrMaterial, channel);
|
|
83
|
+
|
|
84
|
+
// dispose old texture
|
|
85
|
+
texture.dispose();
|
|
86
|
+
} else {
|
|
87
|
+
// no texture, or wrong type => create texture from scratch
|
|
88
|
+
// first we check if some settings were provided in the material definition
|
|
89
|
+
const addMatSettings = await window.Cbn?.Assets.getMaterial(pbrMaterial.id);
|
|
90
|
+
const textureSettings = addMatSettings ? _getTextureObjFromParameterChannel(addMatSettings, channel) : {};
|
|
91
|
+
|
|
92
|
+
// overwrite the name with the url, as this is what the texture parser expects
|
|
93
|
+
(textureSettings as any).name = url;
|
|
94
|
+
// use parser instead of constructor to keep the original texture settings
|
|
95
|
+
const newTexture = Texture.Parse(textureSettings, scene, '') as Texture;
|
|
96
|
+
|
|
97
|
+
if (!newTexture) {
|
|
98
|
+
throw new ViewerError({
|
|
99
|
+
id: ViewerErrorIds.TextureCouldNotBeParsed,
|
|
100
|
+
message: `Texture with url "${url}" could not be parsed`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// await loading of texture
|
|
105
|
+
await new Promise<void>(resolve => BaseTexture.WhenAllReady([newTexture], resolve));
|
|
106
|
+
_assignTextureParameterChannel(newTexture, pbrMaterial, channel);
|
|
107
|
+
|
|
108
|
+
// apply texture settings, which have been set before the texture creation
|
|
109
|
+
await parameterManager.applyTextureSettingsParameter(pbrMaterial, channel);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
parameterManager.setParameterObserver(
|
|
115
|
+
`${channel}.${BuiltInTextureParameter.uOffset}`,
|
|
116
|
+
async ({ newValue, materials }) => {
|
|
117
|
+
const uOffset = ParameterManager.parseNumber(newValue);
|
|
118
|
+
|
|
119
|
+
for (const material of materials) {
|
|
120
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.uOffset);
|
|
121
|
+
|
|
122
|
+
_setUVTextureSetting('uOffset', uOffset, pbrMaterial, channel);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
parameterManager.setParameterObserver(
|
|
127
|
+
`${channel}.${BuiltInTextureParameter.vOffset}`,
|
|
128
|
+
async ({ newValue, materials }) => {
|
|
129
|
+
const vOffset = ParameterManager.parseNumber(newValue);
|
|
130
|
+
|
|
131
|
+
for (const material of materials) {
|
|
132
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.vOffset);
|
|
133
|
+
|
|
134
|
+
_setUVTextureSetting('vOffset', vOffset, pbrMaterial, channel);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
parameterManager.setParameterObserver(
|
|
139
|
+
`${channel}.${BuiltInTextureParameter.uScale}`,
|
|
140
|
+
async ({ newValue, materials }) => {
|
|
141
|
+
const uScale = ParameterManager.parseNumber(newValue);
|
|
142
|
+
|
|
143
|
+
for (const material of materials) {
|
|
144
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.uScale);
|
|
145
|
+
|
|
146
|
+
_setUVTextureSetting('uScale', uScale, pbrMaterial, channel);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
parameterManager.setParameterObserver(
|
|
151
|
+
`${channel}.${BuiltInTextureParameter.vScale}`,
|
|
152
|
+
async ({ newValue, materials }) => {
|
|
153
|
+
const vScale = ParameterManager.parseNumber(newValue);
|
|
154
|
+
|
|
155
|
+
for (const material of materials) {
|
|
156
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.vScale);
|
|
157
|
+
|
|
158
|
+
_setUVTextureSetting('vScale', vScale, pbrMaterial, channel);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
parameterManager.setParameterObserver(
|
|
163
|
+
`${channel}.${BuiltInTextureParameter.uAng}`,
|
|
164
|
+
async ({ newValue, materials }) => {
|
|
165
|
+
const uAng = ParameterManager.parseNumber(newValue);
|
|
166
|
+
|
|
167
|
+
for (const material of materials) {
|
|
168
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.uAng);
|
|
169
|
+
|
|
170
|
+
_setUVTextureSetting('uAng', uAng, pbrMaterial, channel);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
parameterManager.setParameterObserver(
|
|
175
|
+
`${channel}.${BuiltInTextureParameter.vAng}`,
|
|
176
|
+
async ({ newValue, materials }) => {
|
|
177
|
+
const vAng = ParameterManager.parseNumber(newValue);
|
|
178
|
+
|
|
179
|
+
for (const material of materials) {
|
|
180
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.vAng);
|
|
181
|
+
|
|
182
|
+
_setUVTextureSetting('vAng', vAng, pbrMaterial, channel);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
parameterManager.setParameterObserver(
|
|
187
|
+
`${channel}.${BuiltInTextureParameter.wAng}`,
|
|
188
|
+
async ({ newValue, materials }) => {
|
|
189
|
+
const wAng = ParameterManager.parseNumber(newValue);
|
|
190
|
+
|
|
191
|
+
for (const material of materials) {
|
|
192
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.wAng);
|
|
193
|
+
|
|
194
|
+
_setUVTextureSetting('wAng', wAng, pbrMaterial, channel);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
parameterManager.setParameterObserver(
|
|
199
|
+
`${channel}.${BuiltInTextureParameter.clampU}`,
|
|
200
|
+
async ({ newValue, materials }) => {
|
|
201
|
+
const clampU = ParameterManager.parseBoolean(newValue);
|
|
202
|
+
|
|
203
|
+
for (const material of materials) {
|
|
204
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.clampU);
|
|
205
|
+
|
|
206
|
+
// we don't use _setUVTextureSetting here as the parameter ("clampU" - boolean) doesn't align with the actual
|
|
207
|
+
// texture setting ("wrapU" - enumeration)
|
|
208
|
+
const texture = _getTextureFromParameterChannel(pbrMaterial, channel);
|
|
209
|
+
if (texture) {
|
|
210
|
+
texture.wrapU = clampU ? Texture.CLAMP_ADDRESSMODE : Texture.WRAP_ADDRESSMODE;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
parameterManager.setParameterObserver(
|
|
216
|
+
`${channel}.${BuiltInTextureParameter.clampV}`,
|
|
217
|
+
async ({ newValue, materials }) => {
|
|
218
|
+
const clampV = ParameterManager.parseBoolean(newValue);
|
|
219
|
+
|
|
220
|
+
for (const material of materials) {
|
|
221
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.clampV);
|
|
222
|
+
|
|
223
|
+
const texture = _getTextureFromParameterChannel(pbrMaterial, channel);
|
|
224
|
+
if (texture) {
|
|
225
|
+
texture.wrapV = clampV ? Texture.CLAMP_ADDRESSMODE : Texture.WRAP_ADDRESSMODE;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
parameterManager.setParameterObserver(
|
|
231
|
+
`${channel}.${BuiltInTextureParameter.uvSet}`,
|
|
232
|
+
async ({ newValue, materials }) => {
|
|
233
|
+
const uvSet = ParameterManager.parseNumber(newValue);
|
|
234
|
+
|
|
235
|
+
for (const material of materials) {
|
|
236
|
+
const pbrMaterial = _assertAndConvertPBRMaterial(material, channel, BuiltInTextureParameter.uvSet);
|
|
237
|
+
|
|
238
|
+
const texture = _getTextureFromParameterChannel(pbrMaterial, channel);
|
|
239
|
+
if (texture) {
|
|
240
|
+
texture.coordinatesIndex = uvSet;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// this parameter is required for activating the detailmap texture, as only assigning the texture is not enough
|
|
248
|
+
parameterManager.setParameterObserver(BuiltInParameter.UseDetailmap, async ({ newValue, materials }) => {
|
|
249
|
+
const useDetailmap = ParameterManager.parseBoolean(newValue);
|
|
250
|
+
|
|
251
|
+
for (const material of materials) {
|
|
252
|
+
const materialCls = material.getClassName();
|
|
253
|
+
if (materialCls !== 'PBRMaterial') {
|
|
254
|
+
throw new Error(`Enabling detailmap for material of instance "${materialCls}" not implemented`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const pbrMaterial = material as PBRMaterial;
|
|
258
|
+
pbrMaterial.detailMap.isEnabled = useDetailmap;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Help function for casting a material into a PBRMaterial type.\
|
|
265
|
+
* Only works if the class name confirms that this is a PBR material, throws an error for any other material type.
|
|
266
|
+
*
|
|
267
|
+
* @param channel only used for error message
|
|
268
|
+
* @param parameter only used for error message
|
|
269
|
+
*/
|
|
270
|
+
function _assertAndConvertPBRMaterial(material: Material, channel: string, parameter: string): PBRMaterial {
|
|
271
|
+
const materialCls = material.getClassName();
|
|
272
|
+
if (materialCls !== 'PBRMaterial') {
|
|
273
|
+
throw new Error(`Setting ${channel} ${parameter} for material of instance "${materialCls}" not implemented`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return material as PBRMaterial;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Help function for adjusting "simple" UV texture settings, as described in the typing of `setting`
|
|
281
|
+
*/
|
|
282
|
+
function _setUVTextureSetting(
|
|
283
|
+
setting: keyof Pick<Texture, 'uOffset' | 'vOffset' | 'uScale' | 'vScale' | 'uAng' | 'vAng' | 'wAng'>,
|
|
284
|
+
value: number,
|
|
285
|
+
pbrMaterial: PBRMaterial,
|
|
286
|
+
channel: ParameterTextureChannelsKeys
|
|
287
|
+
): void {
|
|
288
|
+
const texture = _getTextureFromParameterChannel(pbrMaterial, channel);
|
|
289
|
+
|
|
290
|
+
if (texture) {
|
|
291
|
+
texture[setting] = value;
|
|
292
|
+
} else {
|
|
293
|
+
// this is fine, the texture may be created in a later step
|
|
294
|
+
// texture settings are applied automatically after the creation is done
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Get the runtime texture object from a certain channel in the material.\
|
|
300
|
+
* Only returns the texture if it's from type "Texture".
|
|
301
|
+
*/
|
|
302
|
+
function _getTextureFromParameterChannel(
|
|
303
|
+
pbrMaterial: PBRMaterial,
|
|
304
|
+
channel: ParameterTextureChannelsKeys
|
|
305
|
+
): Texture | undefined {
|
|
306
|
+
let baseTexture: BaseTexture | null;
|
|
307
|
+
|
|
308
|
+
if (channel === 'metallicRoughnessTexture') {
|
|
309
|
+
baseTexture = pbrMaterial.metallicTexture;
|
|
310
|
+
} else if (channel === 'detailmapTexture') {
|
|
311
|
+
baseTexture = pbrMaterial.detailMap.texture;
|
|
312
|
+
} else {
|
|
313
|
+
// most times it's a direct assignment, special cases are handled above
|
|
314
|
+
baseTexture = pbrMaterial[channel];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// only return "Texture" types
|
|
318
|
+
return baseTexture instanceof Texture ? baseTexture : undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Assign runtime texture to the dedicated channel in the PBR material
|
|
323
|
+
*/
|
|
324
|
+
function _assignTextureParameterChannel(
|
|
325
|
+
texture: Texture,
|
|
326
|
+
pbrMaterial: PBRMaterial,
|
|
327
|
+
channel: ParameterTextureChannelsKeys
|
|
328
|
+
): void {
|
|
329
|
+
if (channel === 'metallicRoughnessTexture') {
|
|
330
|
+
pbrMaterial.metallicTexture = texture;
|
|
331
|
+
} else if (channel === 'detailmapTexture') {
|
|
332
|
+
pbrMaterial.detailMap.texture = texture;
|
|
333
|
+
} else {
|
|
334
|
+
pbrMaterial[channel] = texture;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Similar to `_getTextureFromParameterChannel`, whereas the input is a plain JSON object instead of a runtime material
|
|
340
|
+
*/
|
|
341
|
+
function _getTextureObjFromParameterChannel(materialObj: object, channel: ParameterTextureChannelsKeys): object {
|
|
342
|
+
let textureObj: object | undefined;
|
|
343
|
+
|
|
344
|
+
if (channel === 'metallicRoughnessTexture') {
|
|
345
|
+
textureObj = (materialObj as any).metallicTexture;
|
|
346
|
+
} else if (channel === 'detailmapTexture') {
|
|
347
|
+
textureObj = (materialObj as any).detailMap?.texture;
|
|
348
|
+
} else {
|
|
349
|
+
textureObj = (materialObj as any)[channel];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return textureObj ?? {};
|
|
353
|
+
}
|
|
@@ -3,17 +3,23 @@ import {
|
|
|
3
3
|
BaseTexture,
|
|
4
4
|
Material,
|
|
5
5
|
StandardMaterial,
|
|
6
|
+
TagNamingStrategy,
|
|
6
7
|
Viewer,
|
|
7
8
|
ViewerError,
|
|
8
9
|
ViewerErrorIds,
|
|
9
10
|
ViewerEvent,
|
|
10
11
|
} from '../index';
|
|
12
|
+
import * as CloningHelper from '../internal/cloning-helper';
|
|
11
13
|
import {
|
|
12
14
|
clearInternalMetadataValue,
|
|
13
15
|
getInternalMetadataValue,
|
|
14
16
|
setInternalMetadataValue,
|
|
15
17
|
} from '../internal/metadata-helper';
|
|
16
18
|
|
|
19
|
+
export type MaterialCloneOptions = {
|
|
20
|
+
tagNamingStrategy?: TagNamingStrategy;
|
|
21
|
+
};
|
|
22
|
+
|
|
17
23
|
/**
|
|
18
24
|
* Manager for material related tasks
|
|
19
25
|
*/
|
|
@@ -24,6 +30,7 @@ export class MaterialManager {
|
|
|
24
30
|
*/
|
|
25
31
|
public static readonly CBN_FALLBACK_MATERIAL_NAME = '$fallback';
|
|
26
32
|
|
|
33
|
+
protected _clonedMaterials: { [name: string]: Material } = {};
|
|
27
34
|
protected _createMaterialPromises: { [materialId: string]: Promise<Material | null> } = {};
|
|
28
35
|
|
|
29
36
|
/** @internal */
|
|
@@ -89,6 +96,61 @@ export class MaterialManager {
|
|
|
89
96
|
return chosenMaterial as Material;
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Creates a clone of a material.\
|
|
101
|
+
* The clones tags will be adjusted according to the "tagNamingStrategy" option.
|
|
102
|
+
* Predefined material parameters will be applied to the material right away.
|
|
103
|
+
*/
|
|
104
|
+
public async cloneMaterial(
|
|
105
|
+
materialId: string,
|
|
106
|
+
newMaterialId: string,
|
|
107
|
+
options?: MaterialCloneOptions
|
|
108
|
+
): Promise<Material> {
|
|
109
|
+
const existingMaterial = this._clonedMaterials[newMaterialId];
|
|
110
|
+
if (existingMaterial) {
|
|
111
|
+
throw new ViewerError({
|
|
112
|
+
id: ViewerErrorIds.MaterialAlreadyExists,
|
|
113
|
+
message: `Can't create clone as material "${newMaterialId}" already exists`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sourceMaterial = await this.getOrCreateMaterial(materialId);
|
|
118
|
+
const clonedMaterial = CloningHelper.cloneMaterial(sourceMaterial, newMaterialId, options?.tagNamingStrategy);
|
|
119
|
+
|
|
120
|
+
await this.viewer.parameterManager.applyParameterValuesToMaterial(clonedMaterial);
|
|
121
|
+
|
|
122
|
+
this._clonedMaterials[newMaterialId] = clonedMaterial;
|
|
123
|
+
|
|
124
|
+
return clonedMaterial;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Removes a cloned material from the scene and from the internal material clone storage.\
|
|
129
|
+
* Also remove all parameter entries from this material.
|
|
130
|
+
*/
|
|
131
|
+
public deleteClonedMaterial(materialId: string): void {
|
|
132
|
+
const material = this._clonedMaterials[materialId];
|
|
133
|
+
if (!material) {
|
|
134
|
+
throw new ViewerError({
|
|
135
|
+
id: ViewerErrorIds.NotAClonedMaterial,
|
|
136
|
+
message: `Can't delete material "${materialId}" as there is no cloned material with that id`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.viewer.parameterManager.removeParameterEntriesOfMaterial(material);
|
|
141
|
+
|
|
142
|
+
material.dispose(true, true);
|
|
143
|
+
|
|
144
|
+
delete this._clonedMaterials[materialId];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Removes all cloned materials from the scene and the internal material clone storage
|
|
149
|
+
*/
|
|
150
|
+
public deleteAllClonedMaterials(): void {
|
|
151
|
+
Object.keys(this._clonedMaterials).forEach(materialId => this.deleteClonedMaterial(materialId));
|
|
152
|
+
}
|
|
153
|
+
|
|
92
154
|
protected async _createMaterial(materialId: string, mesh?: AbstractMesh): Promise<Material> {
|
|
93
155
|
if (materialId === MaterialManager.CBN_FALLBACK_MATERIAL_NAME) {
|
|
94
156
|
const fallbackMaterial = new StandardMaterial(MaterialManager.CBN_FALLBACK_MATERIAL_NAME, this.viewer.scene);
|
|
@@ -61,9 +61,7 @@ export class ModelManager {
|
|
|
61
61
|
*/
|
|
62
62
|
public static readonly CBN_FALLBACK_MODEL_ASSET_NAME = '$fallback';
|
|
63
63
|
|
|
64
|
-
protected _modelAssets: {
|
|
65
|
-
[name: string]: Model;
|
|
66
|
-
} = {};
|
|
64
|
+
protected _modelAssets: { [name: string]: Model } = {};
|
|
67
65
|
protected _fallbackModelAsset: Model = {
|
|
68
66
|
name: ModelManager.CBN_FALLBACK_MODEL_ASSET_NAME,
|
|
69
67
|
url: '',
|
|
@@ -239,13 +237,14 @@ export class ModelManager {
|
|
|
239
237
|
*
|
|
240
238
|
* @param show show model immediately after cloning
|
|
241
239
|
* @param options additional options for the cloning procedure, like renaming algorithms
|
|
240
|
+
* @returns asset container for further processing of the clone
|
|
242
241
|
*/
|
|
243
242
|
public async cloneModel(
|
|
244
243
|
name: string,
|
|
245
244
|
newModelName: string,
|
|
246
245
|
show: boolean = true,
|
|
247
246
|
options?: ModelCloneOptions
|
|
248
|
-
): Promise<
|
|
247
|
+
): Promise<AssetContainer> {
|
|
249
248
|
const sourceModel = this._getModel(name);
|
|
250
249
|
if (!sourceModel) {
|
|
251
250
|
throw new ViewerError({
|
|
@@ -288,10 +287,13 @@ export class ModelManager {
|
|
|
288
287
|
if (show) {
|
|
289
288
|
await this._showModel(clonedModel);
|
|
290
289
|
}
|
|
290
|
+
|
|
291
|
+
return clonedModel.assetContainer;
|
|
291
292
|
}
|
|
292
293
|
|
|
293
294
|
/**
|
|
294
295
|
* Removes a cloned model from the scene and from the internal model storage.\
|
|
296
|
+
* Also removes parameter entries from all nodes of the model.\
|
|
295
297
|
* Deleted clones can not be shown again.
|
|
296
298
|
*/
|
|
297
299
|
public deleteClonedModel(name: string): void {
|
|
@@ -310,13 +312,17 @@ export class ModelManager {
|
|
|
310
312
|
});
|
|
311
313
|
}
|
|
312
314
|
|
|
315
|
+
// parameter entries of deleted model can get in the way when recreating a clone with the same node names
|
|
316
|
+
// that's why it's benefitial to wipe the parameter entries of the model as well after removing it
|
|
317
|
+
this.viewer.parameterManager.removeParameterEntriesOfModel(model.assetContainer);
|
|
318
|
+
|
|
313
319
|
model.assetContainer.dispose();
|
|
314
320
|
|
|
315
321
|
delete this._modelAssets[name];
|
|
316
322
|
}
|
|
317
323
|
|
|
318
324
|
/**
|
|
319
|
-
* Removes all cloned models from the scene and the internal model storage
|
|
325
|
+
* Removes all cloned models from the scene and the internal model storage
|
|
320
326
|
*/
|
|
321
327
|
public deleteAllClonedModels(): void {
|
|
322
328
|
const clonedModelNames = Object.values(this._modelAssets)
|