@promptbook/node 0.112.0-134 → 0.112.0-136
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/esm/index.es.js +795 -39
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/core.index.d.ts +0 -2
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +795 -39
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/core.index.d.ts +0 -2
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* @generated
|
|
52
52
|
* @see https://github.com/webgptorg/promptbook
|
|
53
53
|
*/
|
|
54
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
54
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-136';
|
|
55
55
|
/**
|
|
56
56
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
57
57
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -12329,6 +12329,11 @@
|
|
|
12329
12329
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
12330
12330
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
12331
12331
|
*
|
|
12332
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
12333
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
12334
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
12335
|
+
* outbound request.
|
|
12336
|
+
*
|
|
12332
12337
|
* @public exported from `@promptbook/utils`
|
|
12333
12338
|
*/
|
|
12334
12339
|
function isValidAgentUrl(url) {
|
|
@@ -12342,12 +12347,6 @@
|
|
|
12342
12347
|
// TODO: [🐠]
|
|
12343
12348
|
return false;
|
|
12344
12349
|
}
|
|
12345
|
-
/*
|
|
12346
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
12347
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
12348
|
-
return false;
|
|
12349
|
-
}
|
|
12350
|
-
*/
|
|
12351
12350
|
return true;
|
|
12352
12351
|
}
|
|
12353
12352
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -15977,7 +15976,7 @@
|
|
|
15977
15976
|
*
|
|
15978
15977
|
* @private helper of `minecraft2AvatarVisual`
|
|
15979
15978
|
*/
|
|
15980
|
-
const LIGHT_DIRECTION$
|
|
15979
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
15981
15980
|
x: 0.4,
|
|
15982
15981
|
y: -0.65,
|
|
15983
15982
|
z: 0.92,
|
|
@@ -16201,7 +16200,7 @@
|
|
|
16201
16200
|
corners: projectedCorners,
|
|
16202
16201
|
texture: faceDefinition.texture,
|
|
16203
16202
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
16204
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
16203
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
16205
16204
|
outlineColor: cuboid.outlineColor,
|
|
16206
16205
|
};
|
|
16207
16206
|
});
|
|
@@ -16888,7 +16887,7 @@
|
|
|
16888
16887
|
*
|
|
16889
16888
|
* @private helper of `octopus3AvatarVisual`
|
|
16890
16889
|
*/
|
|
16891
|
-
function formatAlphaHex$
|
|
16890
|
+
function formatAlphaHex$2(opacity) {
|
|
16892
16891
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
16893
16892
|
.toString(16)
|
|
16894
16893
|
.padStart(2, '0');
|
|
@@ -17267,7 +17266,7 @@
|
|
|
17267
17266
|
context.beginPath();
|
|
17268
17267
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
17269
17268
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
17270
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
17269
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
17271
17270
|
context.lineWidth = radiusX * 0.08;
|
|
17272
17271
|
context.lineCap = 'round';
|
|
17273
17272
|
context.stroke();
|
|
@@ -17348,7 +17347,7 @@
|
|
|
17348
17347
|
context.beginPath();
|
|
17349
17348
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
17350
17349
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
17351
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
17350
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
17352
17351
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
17353
17352
|
context.lineCap = 'round';
|
|
17354
17353
|
context.stroke();
|
|
@@ -17394,7 +17393,7 @@
|
|
|
17394
17393
|
*
|
|
17395
17394
|
* @private helper of the 3D octopus avatar visuals
|
|
17396
17395
|
*/
|
|
17397
|
-
function formatAlphaHex(opacity) {
|
|
17396
|
+
function formatAlphaHex$1(opacity) {
|
|
17398
17397
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
17399
17398
|
.toString(16)
|
|
17400
17399
|
.padStart(2, '0');
|
|
@@ -17406,7 +17405,7 @@
|
|
|
17406
17405
|
*
|
|
17407
17406
|
* @private helper of `octopus3dAvatarVisual`
|
|
17408
17407
|
*/
|
|
17409
|
-
const LIGHT_DIRECTION$
|
|
17408
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
17410
17409
|
x: 0.48,
|
|
17411
17410
|
y: -0.62,
|
|
17412
17411
|
z: 0.94,
|
|
@@ -17656,7 +17655,7 @@
|
|
|
17656
17655
|
corners: projectedCorners,
|
|
17657
17656
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
17658
17657
|
transformedCorners.length,
|
|
17659
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
17658
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
17660
17659
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
17661
17660
|
outlineColor,
|
|
17662
17661
|
});
|
|
@@ -17845,7 +17844,7 @@
|
|
|
17845
17844
|
*
|
|
17846
17845
|
* @private helper of `octopus3d2AvatarVisual`
|
|
17847
17846
|
*/
|
|
17848
|
-
const LIGHT_DIRECTION$
|
|
17847
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
17849
17848
|
x: 0.38,
|
|
17850
17849
|
y: -0.6,
|
|
17851
17850
|
z: 0.98,
|
|
@@ -18004,13 +18003,13 @@
|
|
|
18004
18003
|
*
|
|
18005
18004
|
* @private helper of `octopus3d2AvatarVisual`
|
|
18006
18005
|
*/
|
|
18007
|
-
const LATITUDE_PATCH_COUNT$
|
|
18006
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
18008
18007
|
/**
|
|
18009
18008
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
18010
18009
|
*
|
|
18011
18010
|
* @private helper of `octopus3d2AvatarVisual`
|
|
18012
18011
|
*/
|
|
18013
|
-
const LONGITUDE_PATCH_COUNT$
|
|
18012
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
18014
18013
|
/**
|
|
18015
18014
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
18016
18015
|
*
|
|
@@ -18024,8 +18023,8 @@
|
|
|
18024
18023
|
*/
|
|
18025
18024
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
18026
18025
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
18027
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
18028
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
18026
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
18027
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
18029
18028
|
const surfacePatches = [];
|
|
18030
18029
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
18031
18030
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -18084,8 +18083,8 @@
|
|
|
18084
18083
|
transformedCorners[2].z +
|
|
18085
18084
|
transformedCorners[3].z) /
|
|
18086
18085
|
4,
|
|
18087
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
18088
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
18086
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
18087
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
18089
18088
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
18090
18089
|
});
|
|
18091
18090
|
}
|
|
@@ -18153,7 +18152,7 @@
|
|
|
18153
18152
|
*
|
|
18154
18153
|
* @private helper of `octopus3d2AvatarVisual`
|
|
18155
18154
|
*/
|
|
18156
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
18155
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
18157
18156
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
18158
18157
|
if (tonalProgress < 0.16) {
|
|
18159
18158
|
return palette.highlight;
|
|
@@ -18199,7 +18198,7 @@
|
|
|
18199
18198
|
*
|
|
18200
18199
|
* @private helper of `octopus3d3AvatarVisual`
|
|
18201
18200
|
*/
|
|
18202
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
18201
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
18203
18202
|
x: 0.34,
|
|
18204
18203
|
y: -0.62,
|
|
18205
18204
|
z: 1,
|
|
@@ -18209,7 +18208,7 @@
|
|
|
18209
18208
|
*
|
|
18210
18209
|
* @private helper of `octopus3d3AvatarVisual`
|
|
18211
18210
|
*/
|
|
18212
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
18211
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
18213
18212
|
/**
|
|
18214
18213
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
18215
18214
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -18348,9 +18347,9 @@
|
|
|
18348
18347
|
* @private helper of `octopus3d3AvatarVisual`
|
|
18349
18348
|
*/
|
|
18350
18349
|
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
18351
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
18350
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
18352
18351
|
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
18353
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
18352
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
18354
18353
|
return {
|
|
18355
18354
|
centerLongitude: -Math.PI * 0.86 +
|
|
18356
18355
|
progress * Math.PI * 1.72 +
|
|
@@ -18417,13 +18416,13 @@
|
|
|
18417
18416
|
*
|
|
18418
18417
|
* @private helper of `octopus3d3AvatarVisual`
|
|
18419
18418
|
*/
|
|
18420
|
-
const LATITUDE_PATCH_COUNT = 16;
|
|
18419
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
18421
18420
|
/**
|
|
18422
18421
|
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
18423
18422
|
*
|
|
18424
18423
|
* @private helper of `octopus3d3AvatarVisual`
|
|
18425
18424
|
*/
|
|
18426
|
-
const LONGITUDE_PATCH_COUNT = 40;
|
|
18425
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
18427
18426
|
/**
|
|
18428
18427
|
* Resolves visible projected patches for the continuous octopus mesh.
|
|
18429
18428
|
*
|
|
@@ -18437,8 +18436,8 @@
|
|
|
18437
18436
|
*/
|
|
18438
18437
|
function resolveVisibleContinuousOctopusPatches(options) {
|
|
18439
18438
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
18440
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
18441
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
18439
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
18440
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
18442
18441
|
const surfacePatches = [];
|
|
18443
18442
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
18444
18443
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -18506,7 +18505,7 @@
|
|
|
18506
18505
|
transformedCorners[2].z +
|
|
18507
18506
|
transformedCorners[3].z) /
|
|
18508
18507
|
4,
|
|
18509
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
18508
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
18510
18509
|
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
18511
18510
|
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
18512
18511
|
});
|
|
@@ -18540,9 +18539,9 @@
|
|
|
18540
18539
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
18541
18540
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
18542
18541
|
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
18543
|
-
const lowerBlend = smoothStep(0.38, 1, verticalProgress);
|
|
18544
|
-
const tipBlend = smoothStep(0.68, 1, verticalProgress);
|
|
18545
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
18542
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
18543
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
18544
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
18546
18545
|
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
18547
18546
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
18548
18547
|
animationPhase * 0.6 +
|
|
@@ -18608,7 +18607,7 @@
|
|
|
18608
18607
|
let weightedDepthScale = 0;
|
|
18609
18608
|
let weightedPhase = 0;
|
|
18610
18609
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
18611
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
18610
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
18612
18611
|
const width = 0.2 * tentacleProfile.widthScale;
|
|
18613
18612
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
18614
18613
|
totalWeight += weight;
|
|
@@ -18648,7 +18647,7 @@
|
|
|
18648
18647
|
*/
|
|
18649
18648
|
function resolveContinuousLobeWave(options, longitude) {
|
|
18650
18649
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
18651
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
18650
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
18652
18651
|
animationPhase +
|
|
18653
18652
|
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
18654
18653
|
1) /
|
|
@@ -18802,7 +18801,7 @@
|
|
|
18802
18801
|
*
|
|
18803
18802
|
* @private helper of `octopus3d3AvatarVisual`
|
|
18804
18803
|
*/
|
|
18805
|
-
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
18804
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
18806
18805
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
18807
18806
|
}
|
|
18808
18807
|
/**
|
|
@@ -18810,11 +18809,767 @@
|
|
|
18810
18809
|
*
|
|
18811
18810
|
* @private helper of `octopus3d3AvatarVisual`
|
|
18812
18811
|
*/
|
|
18813
|
-
function smoothStep(edgeStart, edgeEnd, value) {
|
|
18812
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
18814
18813
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
18815
18814
|
return progress * progress * (3 - 2 * progress);
|
|
18816
18815
|
}
|
|
18817
18816
|
|
|
18817
|
+
/* eslint-disable no-magic-numbers */
|
|
18818
|
+
/**
|
|
18819
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
18820
|
+
*
|
|
18821
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18822
|
+
*/
|
|
18823
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
18824
|
+
x: 0.32,
|
|
18825
|
+
y: -0.66,
|
|
18826
|
+
z: 1,
|
|
18827
|
+
});
|
|
18828
|
+
/**
|
|
18829
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
18830
|
+
*
|
|
18831
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18832
|
+
*/
|
|
18833
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
18834
|
+
x: -0.7,
|
|
18835
|
+
y: -0.18,
|
|
18836
|
+
z: 0.55,
|
|
18837
|
+
});
|
|
18838
|
+
/**
|
|
18839
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
18840
|
+
*
|
|
18841
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18842
|
+
*/
|
|
18843
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
18844
|
+
/**
|
|
18845
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
18846
|
+
*
|
|
18847
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18848
|
+
*/
|
|
18849
|
+
const SKIN_SPOT_COUNT = 14;
|
|
18850
|
+
/**
|
|
18851
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
18852
|
+
*
|
|
18853
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18854
|
+
*/
|
|
18855
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
18856
|
+
/**
|
|
18857
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
18858
|
+
*
|
|
18859
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18860
|
+
*/
|
|
18861
|
+
function getOctopus3d4StableState(createRandom) {
|
|
18862
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
18863
|
+
if (cached !== undefined) {
|
|
18864
|
+
return cached;
|
|
18865
|
+
}
|
|
18866
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
18867
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
18868
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
18869
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
18870
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
18871
|
+
const state = {
|
|
18872
|
+
morphologyProfile,
|
|
18873
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
18874
|
+
leftEyePhaseOffset,
|
|
18875
|
+
rightEyePhaseOffset,
|
|
18876
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
18877
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
18878
|
+
};
|
|
18879
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
18880
|
+
return state;
|
|
18881
|
+
}
|
|
18882
|
+
/**
|
|
18883
|
+
* Octopus 3D 4 avatar visual.
|
|
18884
|
+
*
|
|
18885
|
+
* @private built-in avatar visual
|
|
18886
|
+
*/
|
|
18887
|
+
const octopus3d4AvatarVisual = {
|
|
18888
|
+
id: 'octopus3d4',
|
|
18889
|
+
title: 'Octopus 3D 4',
|
|
18890
|
+
description: 'Cute blobby 3D octopus rendered as one smooth continuous mesh with naturally undulating tentacles, rich multi-pass shading, pigment spots, and cursor-aware head and eyes.',
|
|
18891
|
+
isAnimated: true,
|
|
18892
|
+
supportsPointerTracking: true,
|
|
18893
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
18894
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
18895
|
+
const sceneCenterX = size * 0.5;
|
|
18896
|
+
const sceneCenterY = size * 0.535;
|
|
18897
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
18898
|
+
const meshCenter = {
|
|
18899
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
18900
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
18901
|
+
z: interaction.intensity * size * 0.02,
|
|
18902
|
+
};
|
|
18903
|
+
const rotationY = -0.08 +
|
|
18904
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
18905
|
+
interaction.bodyOffsetX * 0.24 +
|
|
18906
|
+
interaction.gazeX * 0.98;
|
|
18907
|
+
const rotationX = -0.07 +
|
|
18908
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
18909
|
+
interaction.bodyOffsetY * 0.08 -
|
|
18910
|
+
interaction.gazeY * 0.42;
|
|
18911
|
+
const surfaceOptions = {
|
|
18912
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
18913
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
18914
|
+
radiusZ: size *
|
|
18915
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
18916
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
18917
|
+
morphologyProfile,
|
|
18918
|
+
timeMs,
|
|
18919
|
+
animationPhase,
|
|
18920
|
+
tentacleProfiles,
|
|
18921
|
+
};
|
|
18922
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
18923
|
+
...surfaceOptions,
|
|
18924
|
+
center: meshCenter,
|
|
18925
|
+
rotationX,
|
|
18926
|
+
rotationY,
|
|
18927
|
+
sceneCenterX,
|
|
18928
|
+
sceneCenterY,
|
|
18929
|
+
size,
|
|
18930
|
+
palette,
|
|
18931
|
+
});
|
|
18932
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
18933
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
18934
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
18935
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
18936
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
18937
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
18938
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
18939
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
18940
|
+
drawAvatarFrame(context, size, palette);
|
|
18941
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
18942
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
18943
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
18944
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
18945
|
+
}
|
|
18946
|
+
drawBlobbySkinSpots({
|
|
18947
|
+
context,
|
|
18948
|
+
surfaceOptions,
|
|
18949
|
+
center: meshCenter,
|
|
18950
|
+
rotationX,
|
|
18951
|
+
rotationY,
|
|
18952
|
+
sceneCenterX,
|
|
18953
|
+
sceneCenterY,
|
|
18954
|
+
size,
|
|
18955
|
+
palette,
|
|
18956
|
+
skinSpots,
|
|
18957
|
+
});
|
|
18958
|
+
drawBlobbyContinuousCurrents({
|
|
18959
|
+
context,
|
|
18960
|
+
surfaceOptions,
|
|
18961
|
+
center: meshCenter,
|
|
18962
|
+
rotationX,
|
|
18963
|
+
rotationY,
|
|
18964
|
+
sceneCenterX,
|
|
18965
|
+
sceneCenterY,
|
|
18966
|
+
size,
|
|
18967
|
+
palette,
|
|
18968
|
+
morphologyProfile,
|
|
18969
|
+
timeMs,
|
|
18970
|
+
animationPhase,
|
|
18971
|
+
});
|
|
18972
|
+
drawBlobbyContinuousSuckers({
|
|
18973
|
+
context,
|
|
18974
|
+
surfaceOptions,
|
|
18975
|
+
center: meshCenter,
|
|
18976
|
+
rotationX,
|
|
18977
|
+
rotationY,
|
|
18978
|
+
sceneCenterX,
|
|
18979
|
+
sceneCenterY,
|
|
18980
|
+
size,
|
|
18981
|
+
palette,
|
|
18982
|
+
});
|
|
18983
|
+
drawBlobbyContinuousGloss({
|
|
18984
|
+
context,
|
|
18985
|
+
surfaceOptions,
|
|
18986
|
+
center: meshCenter,
|
|
18987
|
+
rotationX,
|
|
18988
|
+
rotationY,
|
|
18989
|
+
sceneCenterX,
|
|
18990
|
+
sceneCenterY,
|
|
18991
|
+
size,
|
|
18992
|
+
palette,
|
|
18993
|
+
});
|
|
18994
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
18995
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
18996
|
+
drawProjectedOrganicMouth(context, [
|
|
18997
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
18998
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
18999
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
19000
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
19001
|
+
},
|
|
19002
|
+
};
|
|
19003
|
+
/**
|
|
19004
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
19005
|
+
*
|
|
19006
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19007
|
+
*/
|
|
19008
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
19009
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
19010
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
19011
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
19012
|
+
return {
|
|
19013
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
19014
|
+
progress * Math.PI * 1.8 +
|
|
19015
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
19016
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
19017
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
19018
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
19019
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
19020
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
19021
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
19022
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
19023
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
19024
|
+
};
|
|
19025
|
+
});
|
|
19026
|
+
}
|
|
19027
|
+
/**
|
|
19028
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
19029
|
+
*
|
|
19030
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19031
|
+
*/
|
|
19032
|
+
function createBlobbySkinSpots(createRandom) {
|
|
19033
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
19034
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
19035
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
19036
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
19037
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
19038
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
19039
|
+
}));
|
|
19040
|
+
}
|
|
19041
|
+
/**
|
|
19042
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
19043
|
+
*
|
|
19044
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19045
|
+
*/
|
|
19046
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
19047
|
+
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.12, sceneCenterY - size * 0.18 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY, size * (0.68 + interaction.intensity * 0.025));
|
|
19048
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
19049
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
19050
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
19051
|
+
context.fillStyle = glowGradient;
|
|
19052
|
+
context.fillRect(0, 0, size, size);
|
|
19053
|
+
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1500) * size * 0.05, sceneCenterY + size * 0.3, size * 0.06, sceneCenterX, sceneCenterY + size * 0.3, size * 0.56);
|
|
19054
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
19055
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
19056
|
+
context.fillStyle = lowerGradient;
|
|
19057
|
+
context.fillRect(0, 0, size, size);
|
|
19058
|
+
}
|
|
19059
|
+
/**
|
|
19060
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
19061
|
+
*
|
|
19062
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19063
|
+
*/
|
|
19064
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
19065
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
19066
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
19067
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
19068
|
+
const ry = size * 0.062;
|
|
19069
|
+
context.save();
|
|
19070
|
+
context.translate(cx, cy);
|
|
19071
|
+
context.scale(1, ry / rx);
|
|
19072
|
+
const blurRadius = rx * 1.42;
|
|
19073
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
19074
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
19075
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
19076
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
19077
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
19078
|
+
context.fillStyle = shadowGradient;
|
|
19079
|
+
context.beginPath();
|
|
19080
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
19081
|
+
context.fill();
|
|
19082
|
+
context.restore();
|
|
19083
|
+
}
|
|
19084
|
+
/**
|
|
19085
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
19086
|
+
*
|
|
19087
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19088
|
+
*/
|
|
19089
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
19090
|
+
/**
|
|
19091
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
19092
|
+
*
|
|
19093
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19094
|
+
*/
|
|
19095
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
19096
|
+
/**
|
|
19097
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
19098
|
+
*
|
|
19099
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
19100
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
19101
|
+
* than re-evaluated for every patch corner.
|
|
19102
|
+
*
|
|
19103
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19104
|
+
*/
|
|
19105
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
19106
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
19107
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
19108
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
19109
|
+
const surfacePatches = [];
|
|
19110
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
19111
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
19112
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
19113
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
19114
|
+
}
|
|
19115
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
19116
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
19117
|
+
}
|
|
19118
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
19119
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
19120
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
19121
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
19122
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
19123
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
19124
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
19125
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
19126
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
19127
|
+
}
|
|
19128
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
19129
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
19130
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
19131
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
19132
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
19133
|
+
}
|
|
19134
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
19135
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
19136
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
19137
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
19138
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
19139
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
19140
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
19141
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
19142
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
19143
|
+
}
|
|
19144
|
+
}
|
|
19145
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
19146
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
19147
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
19148
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
19149
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
19150
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
19151
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
19152
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
19153
|
+
const transformedCorners = [
|
|
19154
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
19155
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
19156
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
19157
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
19158
|
+
];
|
|
19159
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
19160
|
+
if (surfaceNormal.z <= 0.006) {
|
|
19161
|
+
continue;
|
|
19162
|
+
}
|
|
19163
|
+
const projectedCorners = [
|
|
19164
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
19165
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
19166
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
19167
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
19168
|
+
];
|
|
19169
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
19170
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
19171
|
+
surfacePatches.push({
|
|
19172
|
+
corners: projectedCorners,
|
|
19173
|
+
averageDepth: (transformedCorners[0].z +
|
|
19174
|
+
transformedCorners[1].z +
|
|
19175
|
+
transformedCorners[2].z +
|
|
19176
|
+
transformedCorners[3].z) /
|
|
19177
|
+
4,
|
|
19178
|
+
lightIntensity,
|
|
19179
|
+
rimLightIntensity,
|
|
19180
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
19181
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
19182
|
+
});
|
|
19183
|
+
}
|
|
19184
|
+
}
|
|
19185
|
+
return surfacePatches;
|
|
19186
|
+
}
|
|
19187
|
+
/**
|
|
19188
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
19189
|
+
*
|
|
19190
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19191
|
+
*/
|
|
19192
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
19193
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
19194
|
+
}
|
|
19195
|
+
/**
|
|
19196
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
19197
|
+
*
|
|
19198
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
19199
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
19200
|
+
* of `latitudePatchCount * 4` times.
|
|
19201
|
+
*
|
|
19202
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19203
|
+
*/
|
|
19204
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
19205
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
19206
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
19207
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
19208
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
19209
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
19210
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
19211
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
19212
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
19213
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
19214
|
+
animationPhase * 0.6 +
|
|
19215
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
19216
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
19217
|
+
(0.32 + lowerBlend * 0.7);
|
|
19218
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
19219
|
+
lowerBlend *
|
|
19220
|
+
tentacleInfluence.core *
|
|
19221
|
+
tentacleInfluence.swayScale;
|
|
19222
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
19223
|
+
lowerBlend *
|
|
19224
|
+
tentacleInfluence.core *
|
|
19225
|
+
tentacleInfluence.swayScale *
|
|
19226
|
+
0.42;
|
|
19227
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
19228
|
+
tipBlend *
|
|
19229
|
+
tentacleInfluence.core *
|
|
19230
|
+
tentacleInfluence.curlScale *
|
|
19231
|
+
0.36;
|
|
19232
|
+
const horizontalScale = 1.06 +
|
|
19233
|
+
mantleRipple +
|
|
19234
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
19235
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
19236
|
+
upperBlend * 0.08;
|
|
19237
|
+
const depthScale = 1.08 +
|
|
19238
|
+
upperBlend * 0.16 +
|
|
19239
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
19240
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
19241
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
19242
|
+
const tentacleTubeRadius = lowerBlend *
|
|
19243
|
+
tentacleInfluence.core *
|
|
19244
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
19245
|
+
radiusX;
|
|
19246
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
19247
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
19248
|
+
const lowerDrop = lowerBlend *
|
|
19249
|
+
radiusY *
|
|
19250
|
+
(0.2 +
|
|
19251
|
+
tentacleInfluence.core *
|
|
19252
|
+
(0.42 +
|
|
19253
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
19254
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
19255
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
19256
|
+
return {
|
|
19257
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
19258
|
+
combinedTentacleSway +
|
|
19259
|
+
tentacleCurl * radiusX * 0.18,
|
|
19260
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
19261
|
+
upperBlend * radiusY * 0.12 +
|
|
19262
|
+
lowerDrop +
|
|
19263
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
19264
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
19265
|
+
lowerBlend *
|
|
19266
|
+
tipBlend *
|
|
19267
|
+
tentacleInfluence.core *
|
|
19268
|
+
radiusY *
|
|
19269
|
+
0.04,
|
|
19270
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
19271
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
19272
|
+
lowerBlend *
|
|
19273
|
+
tentacleInfluence.core *
|
|
19274
|
+
radiusZ *
|
|
19275
|
+
0.044 +
|
|
19276
|
+
tentacleCurl * radiusZ * 0.14,
|
|
19277
|
+
};
|
|
19278
|
+
}
|
|
19279
|
+
/**
|
|
19280
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
19281
|
+
*
|
|
19282
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19283
|
+
*/
|
|
19284
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
19285
|
+
let totalWeight = 0;
|
|
19286
|
+
let weightedSin = 0;
|
|
19287
|
+
let weightedCos = 0;
|
|
19288
|
+
let weightedWidthScale = 0;
|
|
19289
|
+
let weightedLengthScale = 0;
|
|
19290
|
+
let weightedSwayScale = 0;
|
|
19291
|
+
let weightedDepthScale = 0;
|
|
19292
|
+
let weightedCurlScale = 0;
|
|
19293
|
+
let weightedPrimaryPhase = 0;
|
|
19294
|
+
let weightedSecondaryPhase = 0;
|
|
19295
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
19296
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
19297
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
19298
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
19299
|
+
totalWeight += weight;
|
|
19300
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
19301
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
19302
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
19303
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
19304
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
19305
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
19306
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
19307
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
19308
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
19309
|
+
}
|
|
19310
|
+
if (totalWeight < 0.0001) {
|
|
19311
|
+
return {
|
|
19312
|
+
core: 0,
|
|
19313
|
+
centerLongitude: longitude,
|
|
19314
|
+
widthScale: 1,
|
|
19315
|
+
lengthScale: 1,
|
|
19316
|
+
swayScale: 1,
|
|
19317
|
+
depthScale: 1,
|
|
19318
|
+
curlScale: 1,
|
|
19319
|
+
primaryPhase: 0,
|
|
19320
|
+
secondaryPhase: 0,
|
|
19321
|
+
};
|
|
19322
|
+
}
|
|
19323
|
+
return {
|
|
19324
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
19325
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
19326
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
19327
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
19328
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
19329
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
19330
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
19331
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
19332
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
19333
|
+
};
|
|
19334
|
+
}
|
|
19335
|
+
/**
|
|
19336
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
19337
|
+
*
|
|
19338
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19339
|
+
*/
|
|
19340
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
19341
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
19342
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
19343
|
+
animationPhase +
|
|
19344
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
19345
|
+
1) /
|
|
19346
|
+
2);
|
|
19347
|
+
}
|
|
19348
|
+
/**
|
|
19349
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
19350
|
+
*
|
|
19351
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19352
|
+
*/
|
|
19353
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
19354
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
19355
|
+
if (tonalProgress < 0.12) {
|
|
19356
|
+
return palette.highlight;
|
|
19357
|
+
}
|
|
19358
|
+
if (tonalProgress < 0.3) {
|
|
19359
|
+
return palette.secondary;
|
|
19360
|
+
}
|
|
19361
|
+
if (tonalProgress < 0.72) {
|
|
19362
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
19363
|
+
}
|
|
19364
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
19365
|
+
}
|
|
19366
|
+
/**
|
|
19367
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
19368
|
+
*
|
|
19369
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19370
|
+
*/
|
|
19371
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
19372
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
19373
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
19374
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
19375
|
+
}
|
|
19376
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
19377
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
19378
|
+
}
|
|
19379
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
19380
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
19381
|
+
}
|
|
19382
|
+
context.save();
|
|
19383
|
+
context.beginPath();
|
|
19384
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
19385
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
19386
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
19387
|
+
}
|
|
19388
|
+
context.closePath();
|
|
19389
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
19390
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
19391
|
+
context.lineJoin = 'round';
|
|
19392
|
+
context.stroke();
|
|
19393
|
+
context.restore();
|
|
19394
|
+
}
|
|
19395
|
+
/**
|
|
19396
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
19397
|
+
*
|
|
19398
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19399
|
+
*/
|
|
19400
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
19401
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
19402
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
19403
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
19404
|
+
context.save();
|
|
19405
|
+
context.lineCap = 'round';
|
|
19406
|
+
context.lineJoin = 'round';
|
|
19407
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
19408
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
19409
|
+
const projectedPoints = [];
|
|
19410
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
19411
|
+
const progress = sampleIndex / 8;
|
|
19412
|
+
const latitude = -0.48 + progress * 0.78;
|
|
19413
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
19414
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
19415
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
19416
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
19417
|
+
}
|
|
19418
|
+
}
|
|
19419
|
+
if (projectedPoints.length < 3) {
|
|
19420
|
+
continue;
|
|
19421
|
+
}
|
|
19422
|
+
context.beginPath();
|
|
19423
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
19424
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
19425
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
19426
|
+
}
|
|
19427
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
19428
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
19429
|
+
context.stroke();
|
|
19430
|
+
}
|
|
19431
|
+
context.restore();
|
|
19432
|
+
}
|
|
19433
|
+
/**
|
|
19434
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
19435
|
+
*
|
|
19436
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19437
|
+
*/
|
|
19438
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
19439
|
+
const { surfaceOptions, size } = options;
|
|
19440
|
+
const { timeMs } = surfaceOptions;
|
|
19441
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
19442
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
19443
|
+
continue;
|
|
19444
|
+
}
|
|
19445
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
19446
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
19447
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
19448
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
19449
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
19450
|
+
...options,
|
|
19451
|
+
latitude,
|
|
19452
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
19453
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
19454
|
+
});
|
|
19455
|
+
}
|
|
19456
|
+
}
|
|
19457
|
+
}
|
|
19458
|
+
/**
|
|
19459
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
19460
|
+
*
|
|
19461
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19462
|
+
*/
|
|
19463
|
+
function drawBlobbySkinSpots(options) {
|
|
19464
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
19465
|
+
for (const skinSpot of skinSpots) {
|
|
19466
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
19467
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
19468
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
19469
|
+
continue;
|
|
19470
|
+
}
|
|
19471
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
19472
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
19473
|
+
context.save();
|
|
19474
|
+
context.beginPath();
|
|
19475
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
19476
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
19477
|
+
context.fill();
|
|
19478
|
+
context.restore();
|
|
19479
|
+
}
|
|
19480
|
+
}
|
|
19481
|
+
/**
|
|
19482
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
19483
|
+
*
|
|
19484
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19485
|
+
*/
|
|
19486
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
19487
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
19488
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
19489
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
19490
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
19491
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
19492
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
19493
|
+
return;
|
|
19494
|
+
}
|
|
19495
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
19496
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
19497
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
19498
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
19499
|
+
radiusScale *
|
|
19500
|
+
0.78, size * 0.003, size * 0.02);
|
|
19501
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
19502
|
+
radiusScale *
|
|
19503
|
+
0.54, size * 0.0024, size * 0.015);
|
|
19504
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
19505
|
+
context.save();
|
|
19506
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
19507
|
+
context.rotate(rotation);
|
|
19508
|
+
context.beginPath();
|
|
19509
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
19510
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
19511
|
+
context.fill();
|
|
19512
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
19513
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
19514
|
+
context.stroke();
|
|
19515
|
+
context.restore();
|
|
19516
|
+
}
|
|
19517
|
+
/**
|
|
19518
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
19519
|
+
*
|
|
19520
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19521
|
+
*/
|
|
19522
|
+
function drawBlobbyContinuousGloss(options) {
|
|
19523
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
19524
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
19525
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
19526
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
19527
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
19528
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
19529
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
19530
|
+
return;
|
|
19531
|
+
}
|
|
19532
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
19533
|
+
const glossRadius = size * 0.058;
|
|
19534
|
+
context.save();
|
|
19535
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
19536
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
19537
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
19538
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
19539
|
+
context.fillStyle = glossGradient;
|
|
19540
|
+
context.beginPath();
|
|
19541
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
19542
|
+
context.fill();
|
|
19543
|
+
context.restore();
|
|
19544
|
+
}
|
|
19545
|
+
/**
|
|
19546
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
19547
|
+
*
|
|
19548
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19549
|
+
*/
|
|
19550
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
19551
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
19552
|
+
}
|
|
19553
|
+
/**
|
|
19554
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
19555
|
+
*
|
|
19556
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19557
|
+
*/
|
|
19558
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
19559
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
19560
|
+
return progress * progress * (3 - 2 * progress);
|
|
19561
|
+
}
|
|
19562
|
+
/**
|
|
19563
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
19564
|
+
*
|
|
19565
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
19566
|
+
*/
|
|
19567
|
+
function formatAlphaHex(opacity) {
|
|
19568
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
19569
|
+
.toString(16)
|
|
19570
|
+
.padStart(2, '0');
|
|
19571
|
+
}
|
|
19572
|
+
|
|
18818
19573
|
/* eslint-disable no-magic-numbers */
|
|
18819
19574
|
/**
|
|
18820
19575
|
* Octopus avatar visual.
|
|
@@ -19586,6 +20341,7 @@
|
|
|
19586
20341
|
octopus3dAvatarVisual,
|
|
19587
20342
|
octopus3d2AvatarVisual,
|
|
19588
20343
|
octopus3d3AvatarVisual,
|
|
20344
|
+
octopus3d4AvatarVisual,
|
|
19589
20345
|
asciiOctopusAvatarVisual,
|
|
19590
20346
|
minecraftAvatarVisual,
|
|
19591
20347
|
minecraft2AvatarVisual,
|