@promptbook/core 0.112.0-134 → 0.112.0-135
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/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/package.json +1 -1
- package/umd/index.umd.js +795 -39
- package/umd/index.umd.js.map +1 -1
- 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/index.umd.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @generated
|
|
30
30
|
* @see https://github.com/webgptorg/promptbook
|
|
31
31
|
*/
|
|
32
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
32
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-135';
|
|
33
33
|
/**
|
|
34
34
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
35
35
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -9899,6 +9899,11 @@
|
|
|
9899
9899
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
9900
9900
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
9901
9901
|
*
|
|
9902
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
9903
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
9904
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
9905
|
+
* outbound request.
|
|
9906
|
+
*
|
|
9902
9907
|
* @public exported from `@promptbook/utils`
|
|
9903
9908
|
*/
|
|
9904
9909
|
function isValidAgentUrl(url) {
|
|
@@ -9912,12 +9917,6 @@
|
|
|
9912
9917
|
// TODO: [🐠]
|
|
9913
9918
|
return false;
|
|
9914
9919
|
}
|
|
9915
|
-
/*
|
|
9916
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
9917
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
9918
|
-
return false;
|
|
9919
|
-
}
|
|
9920
|
-
*/
|
|
9921
9920
|
return true;
|
|
9922
9921
|
}
|
|
9923
9922
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -13547,7 +13546,7 @@
|
|
|
13547
13546
|
*
|
|
13548
13547
|
* @private helper of `minecraft2AvatarVisual`
|
|
13549
13548
|
*/
|
|
13550
|
-
const LIGHT_DIRECTION$
|
|
13549
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
13551
13550
|
x: 0.4,
|
|
13552
13551
|
y: -0.65,
|
|
13553
13552
|
z: 0.92,
|
|
@@ -13771,7 +13770,7 @@
|
|
|
13771
13770
|
corners: projectedCorners,
|
|
13772
13771
|
texture: faceDefinition.texture,
|
|
13773
13772
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
13774
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
13773
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
13775
13774
|
outlineColor: cuboid.outlineColor,
|
|
13776
13775
|
};
|
|
13777
13776
|
});
|
|
@@ -14458,7 +14457,7 @@
|
|
|
14458
14457
|
*
|
|
14459
14458
|
* @private helper of `octopus3AvatarVisual`
|
|
14460
14459
|
*/
|
|
14461
|
-
function formatAlphaHex$
|
|
14460
|
+
function formatAlphaHex$2(opacity) {
|
|
14462
14461
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
14463
14462
|
.toString(16)
|
|
14464
14463
|
.padStart(2, '0');
|
|
@@ -14837,7 +14836,7 @@
|
|
|
14837
14836
|
context.beginPath();
|
|
14838
14837
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
14839
14838
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
14840
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
14839
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
14841
14840
|
context.lineWidth = radiusX * 0.08;
|
|
14842
14841
|
context.lineCap = 'round';
|
|
14843
14842
|
context.stroke();
|
|
@@ -14918,7 +14917,7 @@
|
|
|
14918
14917
|
context.beginPath();
|
|
14919
14918
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
14920
14919
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
14921
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
14920
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
14922
14921
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
14923
14922
|
context.lineCap = 'round';
|
|
14924
14923
|
context.stroke();
|
|
@@ -14964,7 +14963,7 @@
|
|
|
14964
14963
|
*
|
|
14965
14964
|
* @private helper of the 3D octopus avatar visuals
|
|
14966
14965
|
*/
|
|
14967
|
-
function formatAlphaHex(opacity) {
|
|
14966
|
+
function formatAlphaHex$1(opacity) {
|
|
14968
14967
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
14969
14968
|
.toString(16)
|
|
14970
14969
|
.padStart(2, '0');
|
|
@@ -14976,7 +14975,7 @@
|
|
|
14976
14975
|
*
|
|
14977
14976
|
* @private helper of `octopus3dAvatarVisual`
|
|
14978
14977
|
*/
|
|
14979
|
-
const LIGHT_DIRECTION$
|
|
14978
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
14980
14979
|
x: 0.48,
|
|
14981
14980
|
y: -0.62,
|
|
14982
14981
|
z: 0.94,
|
|
@@ -15226,7 +15225,7 @@
|
|
|
15226
15225
|
corners: projectedCorners,
|
|
15227
15226
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
15228
15227
|
transformedCorners.length,
|
|
15229
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
15228
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
15230
15229
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
15231
15230
|
outlineColor,
|
|
15232
15231
|
});
|
|
@@ -15415,7 +15414,7 @@
|
|
|
15415
15414
|
*
|
|
15416
15415
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15417
15416
|
*/
|
|
15418
|
-
const LIGHT_DIRECTION$
|
|
15417
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
15419
15418
|
x: 0.38,
|
|
15420
15419
|
y: -0.6,
|
|
15421
15420
|
z: 0.98,
|
|
@@ -15574,13 +15573,13 @@
|
|
|
15574
15573
|
*
|
|
15575
15574
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15576
15575
|
*/
|
|
15577
|
-
const LATITUDE_PATCH_COUNT$
|
|
15576
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
15578
15577
|
/**
|
|
15579
15578
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
15580
15579
|
*
|
|
15581
15580
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15582
15581
|
*/
|
|
15583
|
-
const LONGITUDE_PATCH_COUNT$
|
|
15582
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
15584
15583
|
/**
|
|
15585
15584
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
15586
15585
|
*
|
|
@@ -15594,8 +15593,8 @@
|
|
|
15594
15593
|
*/
|
|
15595
15594
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
15596
15595
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
15597
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
15598
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
15596
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
15597
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
15599
15598
|
const surfacePatches = [];
|
|
15600
15599
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
15601
15600
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -15654,8 +15653,8 @@
|
|
|
15654
15653
|
transformedCorners[2].z +
|
|
15655
15654
|
transformedCorners[3].z) /
|
|
15656
15655
|
4,
|
|
15657
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
15658
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
15656
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
15657
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
15659
15658
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
15660
15659
|
});
|
|
15661
15660
|
}
|
|
@@ -15723,7 +15722,7 @@
|
|
|
15723
15722
|
*
|
|
15724
15723
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15725
15724
|
*/
|
|
15726
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
15725
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
15727
15726
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
15728
15727
|
if (tonalProgress < 0.16) {
|
|
15729
15728
|
return palette.highlight;
|
|
@@ -15769,7 +15768,7 @@
|
|
|
15769
15768
|
*
|
|
15770
15769
|
* @private helper of `octopus3d3AvatarVisual`
|
|
15771
15770
|
*/
|
|
15772
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
15771
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
15773
15772
|
x: 0.34,
|
|
15774
15773
|
y: -0.62,
|
|
15775
15774
|
z: 1,
|
|
@@ -15779,7 +15778,7 @@
|
|
|
15779
15778
|
*
|
|
15780
15779
|
* @private helper of `octopus3d3AvatarVisual`
|
|
15781
15780
|
*/
|
|
15782
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
15781
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
15783
15782
|
/**
|
|
15784
15783
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
15785
15784
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -15918,9 +15917,9 @@
|
|
|
15918
15917
|
* @private helper of `octopus3d3AvatarVisual`
|
|
15919
15918
|
*/
|
|
15920
15919
|
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
15921
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
15920
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
15922
15921
|
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
15923
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
15922
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
15924
15923
|
return {
|
|
15925
15924
|
centerLongitude: -Math.PI * 0.86 +
|
|
15926
15925
|
progress * Math.PI * 1.72 +
|
|
@@ -15987,13 +15986,13 @@
|
|
|
15987
15986
|
*
|
|
15988
15987
|
* @private helper of `octopus3d3AvatarVisual`
|
|
15989
15988
|
*/
|
|
15990
|
-
const LATITUDE_PATCH_COUNT = 16;
|
|
15989
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
15991
15990
|
/**
|
|
15992
15991
|
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
15993
15992
|
*
|
|
15994
15993
|
* @private helper of `octopus3d3AvatarVisual`
|
|
15995
15994
|
*/
|
|
15996
|
-
const LONGITUDE_PATCH_COUNT = 40;
|
|
15995
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
15997
15996
|
/**
|
|
15998
15997
|
* Resolves visible projected patches for the continuous octopus mesh.
|
|
15999
15998
|
*
|
|
@@ -16007,8 +16006,8 @@
|
|
|
16007
16006
|
*/
|
|
16008
16007
|
function resolveVisibleContinuousOctopusPatches(options) {
|
|
16009
16008
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
16010
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
16011
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
16009
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
16010
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
16012
16011
|
const surfacePatches = [];
|
|
16013
16012
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16014
16013
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -16076,7 +16075,7 @@
|
|
|
16076
16075
|
transformedCorners[2].z +
|
|
16077
16076
|
transformedCorners[3].z) /
|
|
16078
16077
|
4,
|
|
16079
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
16078
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
16080
16079
|
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
16081
16080
|
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
16082
16081
|
});
|
|
@@ -16110,9 +16109,9 @@
|
|
|
16110
16109
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
16111
16110
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
16112
16111
|
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
16113
|
-
const lowerBlend = smoothStep(0.38, 1, verticalProgress);
|
|
16114
|
-
const tipBlend = smoothStep(0.68, 1, verticalProgress);
|
|
16115
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
16112
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
16113
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
16114
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
16116
16115
|
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
16117
16116
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
16118
16117
|
animationPhase * 0.6 +
|
|
@@ -16178,7 +16177,7 @@
|
|
|
16178
16177
|
let weightedDepthScale = 0;
|
|
16179
16178
|
let weightedPhase = 0;
|
|
16180
16179
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
16181
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
16180
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
16182
16181
|
const width = 0.2 * tentacleProfile.widthScale;
|
|
16183
16182
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
16184
16183
|
totalWeight += weight;
|
|
@@ -16218,7 +16217,7 @@
|
|
|
16218
16217
|
*/
|
|
16219
16218
|
function resolveContinuousLobeWave(options, longitude) {
|
|
16220
16219
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
16221
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
16220
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
16222
16221
|
animationPhase +
|
|
16223
16222
|
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
16224
16223
|
1) /
|
|
@@ -16372,7 +16371,7 @@
|
|
|
16372
16371
|
*
|
|
16373
16372
|
* @private helper of `octopus3d3AvatarVisual`
|
|
16374
16373
|
*/
|
|
16375
|
-
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
16374
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
16376
16375
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
16377
16376
|
}
|
|
16378
16377
|
/**
|
|
@@ -16380,11 +16379,767 @@
|
|
|
16380
16379
|
*
|
|
16381
16380
|
* @private helper of `octopus3d3AvatarVisual`
|
|
16382
16381
|
*/
|
|
16383
|
-
function smoothStep(edgeStart, edgeEnd, value) {
|
|
16382
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
16384
16383
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
16385
16384
|
return progress * progress * (3 - 2 * progress);
|
|
16386
16385
|
}
|
|
16387
16386
|
|
|
16387
|
+
/* eslint-disable no-magic-numbers */
|
|
16388
|
+
/**
|
|
16389
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
16390
|
+
*
|
|
16391
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16392
|
+
*/
|
|
16393
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
16394
|
+
x: 0.32,
|
|
16395
|
+
y: -0.66,
|
|
16396
|
+
z: 1,
|
|
16397
|
+
});
|
|
16398
|
+
/**
|
|
16399
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
16400
|
+
*
|
|
16401
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16402
|
+
*/
|
|
16403
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
16404
|
+
x: -0.7,
|
|
16405
|
+
y: -0.18,
|
|
16406
|
+
z: 0.55,
|
|
16407
|
+
});
|
|
16408
|
+
/**
|
|
16409
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
16410
|
+
*
|
|
16411
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16412
|
+
*/
|
|
16413
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
16414
|
+
/**
|
|
16415
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
16416
|
+
*
|
|
16417
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16418
|
+
*/
|
|
16419
|
+
const SKIN_SPOT_COUNT = 14;
|
|
16420
|
+
/**
|
|
16421
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
16422
|
+
*
|
|
16423
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16424
|
+
*/
|
|
16425
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
16426
|
+
/**
|
|
16427
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
16428
|
+
*
|
|
16429
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16430
|
+
*/
|
|
16431
|
+
function getOctopus3d4StableState(createRandom) {
|
|
16432
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
16433
|
+
if (cached !== undefined) {
|
|
16434
|
+
return cached;
|
|
16435
|
+
}
|
|
16436
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
16437
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
16438
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
16439
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
16440
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
16441
|
+
const state = {
|
|
16442
|
+
morphologyProfile,
|
|
16443
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16444
|
+
leftEyePhaseOffset,
|
|
16445
|
+
rightEyePhaseOffset,
|
|
16446
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
16447
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
16448
|
+
};
|
|
16449
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
16450
|
+
return state;
|
|
16451
|
+
}
|
|
16452
|
+
/**
|
|
16453
|
+
* Octopus 3D 4 avatar visual.
|
|
16454
|
+
*
|
|
16455
|
+
* @private built-in avatar visual
|
|
16456
|
+
*/
|
|
16457
|
+
const octopus3d4AvatarVisual = {
|
|
16458
|
+
id: 'octopus3d4',
|
|
16459
|
+
title: 'Octopus 3D 4',
|
|
16460
|
+
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.',
|
|
16461
|
+
isAnimated: true,
|
|
16462
|
+
supportsPointerTracking: true,
|
|
16463
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
16464
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
16465
|
+
const sceneCenterX = size * 0.5;
|
|
16466
|
+
const sceneCenterY = size * 0.535;
|
|
16467
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
16468
|
+
const meshCenter = {
|
|
16469
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
16470
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
16471
|
+
z: interaction.intensity * size * 0.02,
|
|
16472
|
+
};
|
|
16473
|
+
const rotationY = -0.08 +
|
|
16474
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
16475
|
+
interaction.bodyOffsetX * 0.24 +
|
|
16476
|
+
interaction.gazeX * 0.98;
|
|
16477
|
+
const rotationX = -0.07 +
|
|
16478
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
16479
|
+
interaction.bodyOffsetY * 0.08 -
|
|
16480
|
+
interaction.gazeY * 0.42;
|
|
16481
|
+
const surfaceOptions = {
|
|
16482
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
16483
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
16484
|
+
radiusZ: size *
|
|
16485
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
16486
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
16487
|
+
morphologyProfile,
|
|
16488
|
+
timeMs,
|
|
16489
|
+
animationPhase,
|
|
16490
|
+
tentacleProfiles,
|
|
16491
|
+
};
|
|
16492
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
16493
|
+
...surfaceOptions,
|
|
16494
|
+
center: meshCenter,
|
|
16495
|
+
rotationX,
|
|
16496
|
+
rotationY,
|
|
16497
|
+
sceneCenterX,
|
|
16498
|
+
sceneCenterY,
|
|
16499
|
+
size,
|
|
16500
|
+
palette,
|
|
16501
|
+
});
|
|
16502
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
16503
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
16504
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
16505
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
16506
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
16507
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
16508
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
16509
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
16510
|
+
drawAvatarFrame(context, size, palette);
|
|
16511
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
16512
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
16513
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
16514
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
16515
|
+
}
|
|
16516
|
+
drawBlobbySkinSpots({
|
|
16517
|
+
context,
|
|
16518
|
+
surfaceOptions,
|
|
16519
|
+
center: meshCenter,
|
|
16520
|
+
rotationX,
|
|
16521
|
+
rotationY,
|
|
16522
|
+
sceneCenterX,
|
|
16523
|
+
sceneCenterY,
|
|
16524
|
+
size,
|
|
16525
|
+
palette,
|
|
16526
|
+
skinSpots,
|
|
16527
|
+
});
|
|
16528
|
+
drawBlobbyContinuousCurrents({
|
|
16529
|
+
context,
|
|
16530
|
+
surfaceOptions,
|
|
16531
|
+
center: meshCenter,
|
|
16532
|
+
rotationX,
|
|
16533
|
+
rotationY,
|
|
16534
|
+
sceneCenterX,
|
|
16535
|
+
sceneCenterY,
|
|
16536
|
+
size,
|
|
16537
|
+
palette,
|
|
16538
|
+
morphologyProfile,
|
|
16539
|
+
timeMs,
|
|
16540
|
+
animationPhase,
|
|
16541
|
+
});
|
|
16542
|
+
drawBlobbyContinuousSuckers({
|
|
16543
|
+
context,
|
|
16544
|
+
surfaceOptions,
|
|
16545
|
+
center: meshCenter,
|
|
16546
|
+
rotationX,
|
|
16547
|
+
rotationY,
|
|
16548
|
+
sceneCenterX,
|
|
16549
|
+
sceneCenterY,
|
|
16550
|
+
size,
|
|
16551
|
+
palette,
|
|
16552
|
+
});
|
|
16553
|
+
drawBlobbyContinuousGloss({
|
|
16554
|
+
context,
|
|
16555
|
+
surfaceOptions,
|
|
16556
|
+
center: meshCenter,
|
|
16557
|
+
rotationX,
|
|
16558
|
+
rotationY,
|
|
16559
|
+
sceneCenterX,
|
|
16560
|
+
sceneCenterY,
|
|
16561
|
+
size,
|
|
16562
|
+
palette,
|
|
16563
|
+
});
|
|
16564
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16565
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16566
|
+
drawProjectedOrganicMouth(context, [
|
|
16567
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
16568
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
16569
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
16570
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
16571
|
+
},
|
|
16572
|
+
};
|
|
16573
|
+
/**
|
|
16574
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
16575
|
+
*
|
|
16576
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16577
|
+
*/
|
|
16578
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
16579
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
16580
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
16581
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
16582
|
+
return {
|
|
16583
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
16584
|
+
progress * Math.PI * 1.8 +
|
|
16585
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
16586
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
16587
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
16588
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
16589
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
16590
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
16591
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
16592
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
16593
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
16594
|
+
};
|
|
16595
|
+
});
|
|
16596
|
+
}
|
|
16597
|
+
/**
|
|
16598
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
16599
|
+
*
|
|
16600
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16601
|
+
*/
|
|
16602
|
+
function createBlobbySkinSpots(createRandom) {
|
|
16603
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
16604
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
16605
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
16606
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
16607
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
16608
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
16609
|
+
}));
|
|
16610
|
+
}
|
|
16611
|
+
/**
|
|
16612
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
16613
|
+
*
|
|
16614
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16615
|
+
*/
|
|
16616
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
16617
|
+
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));
|
|
16618
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
16619
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
16620
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
16621
|
+
context.fillStyle = glowGradient;
|
|
16622
|
+
context.fillRect(0, 0, size, size);
|
|
16623
|
+
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);
|
|
16624
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
16625
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
16626
|
+
context.fillStyle = lowerGradient;
|
|
16627
|
+
context.fillRect(0, 0, size, size);
|
|
16628
|
+
}
|
|
16629
|
+
/**
|
|
16630
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
16631
|
+
*
|
|
16632
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16633
|
+
*/
|
|
16634
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
16635
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
16636
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
16637
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
16638
|
+
const ry = size * 0.062;
|
|
16639
|
+
context.save();
|
|
16640
|
+
context.translate(cx, cy);
|
|
16641
|
+
context.scale(1, ry / rx);
|
|
16642
|
+
const blurRadius = rx * 1.42;
|
|
16643
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
16644
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
16645
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
16646
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
16647
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
16648
|
+
context.fillStyle = shadowGradient;
|
|
16649
|
+
context.beginPath();
|
|
16650
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
16651
|
+
context.fill();
|
|
16652
|
+
context.restore();
|
|
16653
|
+
}
|
|
16654
|
+
/**
|
|
16655
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
16656
|
+
*
|
|
16657
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16658
|
+
*/
|
|
16659
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
16660
|
+
/**
|
|
16661
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
16662
|
+
*
|
|
16663
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16664
|
+
*/
|
|
16665
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
16666
|
+
/**
|
|
16667
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
16668
|
+
*
|
|
16669
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
16670
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
16671
|
+
* than re-evaluated for every patch corner.
|
|
16672
|
+
*
|
|
16673
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16674
|
+
*/
|
|
16675
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
16676
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
16677
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
16678
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
16679
|
+
const surfacePatches = [];
|
|
16680
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16681
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
16682
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
16683
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
16684
|
+
}
|
|
16685
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16686
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
16687
|
+
}
|
|
16688
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
16689
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
16690
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
16691
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
16692
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
16693
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16694
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
16695
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
16696
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
16697
|
+
}
|
|
16698
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16699
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
16700
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
16701
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
16702
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
16703
|
+
}
|
|
16704
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
16705
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
16706
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
16707
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
16708
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
16709
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
16710
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
16711
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
16712
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
16713
|
+
}
|
|
16714
|
+
}
|
|
16715
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
16716
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
16717
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
16718
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
16719
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
16720
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
16721
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
16722
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16723
|
+
const transformedCorners = [
|
|
16724
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
16725
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
16726
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
16727
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
16728
|
+
];
|
|
16729
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
16730
|
+
if (surfaceNormal.z <= 0.006) {
|
|
16731
|
+
continue;
|
|
16732
|
+
}
|
|
16733
|
+
const projectedCorners = [
|
|
16734
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
16735
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
16736
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
16737
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
16738
|
+
];
|
|
16739
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
16740
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
16741
|
+
surfacePatches.push({
|
|
16742
|
+
corners: projectedCorners,
|
|
16743
|
+
averageDepth: (transformedCorners[0].z +
|
|
16744
|
+
transformedCorners[1].z +
|
|
16745
|
+
transformedCorners[2].z +
|
|
16746
|
+
transformedCorners[3].z) /
|
|
16747
|
+
4,
|
|
16748
|
+
lightIntensity,
|
|
16749
|
+
rimLightIntensity,
|
|
16750
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
16751
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
16752
|
+
});
|
|
16753
|
+
}
|
|
16754
|
+
}
|
|
16755
|
+
return surfacePatches;
|
|
16756
|
+
}
|
|
16757
|
+
/**
|
|
16758
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
16759
|
+
*
|
|
16760
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16761
|
+
*/
|
|
16762
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
16763
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
16764
|
+
}
|
|
16765
|
+
/**
|
|
16766
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
16767
|
+
*
|
|
16768
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
16769
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
16770
|
+
* of `latitudePatchCount * 4` times.
|
|
16771
|
+
*
|
|
16772
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16773
|
+
*/
|
|
16774
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
16775
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
16776
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
16777
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
16778
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
16779
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
16780
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
16781
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
16782
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
16783
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
16784
|
+
animationPhase * 0.6 +
|
|
16785
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
16786
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
16787
|
+
(0.32 + lowerBlend * 0.7);
|
|
16788
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
16789
|
+
lowerBlend *
|
|
16790
|
+
tentacleInfluence.core *
|
|
16791
|
+
tentacleInfluence.swayScale;
|
|
16792
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
16793
|
+
lowerBlend *
|
|
16794
|
+
tentacleInfluence.core *
|
|
16795
|
+
tentacleInfluence.swayScale *
|
|
16796
|
+
0.42;
|
|
16797
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
16798
|
+
tipBlend *
|
|
16799
|
+
tentacleInfluence.core *
|
|
16800
|
+
tentacleInfluence.curlScale *
|
|
16801
|
+
0.36;
|
|
16802
|
+
const horizontalScale = 1.06 +
|
|
16803
|
+
mantleRipple +
|
|
16804
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
16805
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
16806
|
+
upperBlend * 0.08;
|
|
16807
|
+
const depthScale = 1.08 +
|
|
16808
|
+
upperBlend * 0.16 +
|
|
16809
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
16810
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
16811
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
16812
|
+
const tentacleTubeRadius = lowerBlend *
|
|
16813
|
+
tentacleInfluence.core *
|
|
16814
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
16815
|
+
radiusX;
|
|
16816
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
16817
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
16818
|
+
const lowerDrop = lowerBlend *
|
|
16819
|
+
radiusY *
|
|
16820
|
+
(0.2 +
|
|
16821
|
+
tentacleInfluence.core *
|
|
16822
|
+
(0.42 +
|
|
16823
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
16824
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
16825
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
16826
|
+
return {
|
|
16827
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
16828
|
+
combinedTentacleSway +
|
|
16829
|
+
tentacleCurl * radiusX * 0.18,
|
|
16830
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
16831
|
+
upperBlend * radiusY * 0.12 +
|
|
16832
|
+
lowerDrop +
|
|
16833
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
16834
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
16835
|
+
lowerBlend *
|
|
16836
|
+
tipBlend *
|
|
16837
|
+
tentacleInfluence.core *
|
|
16838
|
+
radiusY *
|
|
16839
|
+
0.04,
|
|
16840
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
16841
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
16842
|
+
lowerBlend *
|
|
16843
|
+
tentacleInfluence.core *
|
|
16844
|
+
radiusZ *
|
|
16845
|
+
0.044 +
|
|
16846
|
+
tentacleCurl * radiusZ * 0.14,
|
|
16847
|
+
};
|
|
16848
|
+
}
|
|
16849
|
+
/**
|
|
16850
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
16851
|
+
*
|
|
16852
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16853
|
+
*/
|
|
16854
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
16855
|
+
let totalWeight = 0;
|
|
16856
|
+
let weightedSin = 0;
|
|
16857
|
+
let weightedCos = 0;
|
|
16858
|
+
let weightedWidthScale = 0;
|
|
16859
|
+
let weightedLengthScale = 0;
|
|
16860
|
+
let weightedSwayScale = 0;
|
|
16861
|
+
let weightedDepthScale = 0;
|
|
16862
|
+
let weightedCurlScale = 0;
|
|
16863
|
+
let weightedPrimaryPhase = 0;
|
|
16864
|
+
let weightedSecondaryPhase = 0;
|
|
16865
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
16866
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
16867
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
16868
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
16869
|
+
totalWeight += weight;
|
|
16870
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
16871
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
16872
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
16873
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
16874
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
16875
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
16876
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
16877
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
16878
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
16879
|
+
}
|
|
16880
|
+
if (totalWeight < 0.0001) {
|
|
16881
|
+
return {
|
|
16882
|
+
core: 0,
|
|
16883
|
+
centerLongitude: longitude,
|
|
16884
|
+
widthScale: 1,
|
|
16885
|
+
lengthScale: 1,
|
|
16886
|
+
swayScale: 1,
|
|
16887
|
+
depthScale: 1,
|
|
16888
|
+
curlScale: 1,
|
|
16889
|
+
primaryPhase: 0,
|
|
16890
|
+
secondaryPhase: 0,
|
|
16891
|
+
};
|
|
16892
|
+
}
|
|
16893
|
+
return {
|
|
16894
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
16895
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
16896
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
16897
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
16898
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
16899
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
16900
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
16901
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
16902
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
16903
|
+
};
|
|
16904
|
+
}
|
|
16905
|
+
/**
|
|
16906
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
16907
|
+
*
|
|
16908
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16909
|
+
*/
|
|
16910
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
16911
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
16912
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
16913
|
+
animationPhase +
|
|
16914
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
16915
|
+
1) /
|
|
16916
|
+
2);
|
|
16917
|
+
}
|
|
16918
|
+
/**
|
|
16919
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
16920
|
+
*
|
|
16921
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16922
|
+
*/
|
|
16923
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
16924
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
16925
|
+
if (tonalProgress < 0.12) {
|
|
16926
|
+
return palette.highlight;
|
|
16927
|
+
}
|
|
16928
|
+
if (tonalProgress < 0.3) {
|
|
16929
|
+
return palette.secondary;
|
|
16930
|
+
}
|
|
16931
|
+
if (tonalProgress < 0.72) {
|
|
16932
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
16933
|
+
}
|
|
16934
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
16935
|
+
}
|
|
16936
|
+
/**
|
|
16937
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
16938
|
+
*
|
|
16939
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16940
|
+
*/
|
|
16941
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
16942
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
16943
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
16944
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
16945
|
+
}
|
|
16946
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
16947
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
16948
|
+
}
|
|
16949
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
16950
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
16951
|
+
}
|
|
16952
|
+
context.save();
|
|
16953
|
+
context.beginPath();
|
|
16954
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
16955
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
16956
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
16957
|
+
}
|
|
16958
|
+
context.closePath();
|
|
16959
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
16960
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
16961
|
+
context.lineJoin = 'round';
|
|
16962
|
+
context.stroke();
|
|
16963
|
+
context.restore();
|
|
16964
|
+
}
|
|
16965
|
+
/**
|
|
16966
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
16967
|
+
*
|
|
16968
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16969
|
+
*/
|
|
16970
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
16971
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
16972
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
16973
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
16974
|
+
context.save();
|
|
16975
|
+
context.lineCap = 'round';
|
|
16976
|
+
context.lineJoin = 'round';
|
|
16977
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
16978
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
16979
|
+
const projectedPoints = [];
|
|
16980
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
16981
|
+
const progress = sampleIndex / 8;
|
|
16982
|
+
const latitude = -0.48 + progress * 0.78;
|
|
16983
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
16984
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
16985
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
16986
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
16987
|
+
}
|
|
16988
|
+
}
|
|
16989
|
+
if (projectedPoints.length < 3) {
|
|
16990
|
+
continue;
|
|
16991
|
+
}
|
|
16992
|
+
context.beginPath();
|
|
16993
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
16994
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
16995
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
16996
|
+
}
|
|
16997
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
16998
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
16999
|
+
context.stroke();
|
|
17000
|
+
}
|
|
17001
|
+
context.restore();
|
|
17002
|
+
}
|
|
17003
|
+
/**
|
|
17004
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
17005
|
+
*
|
|
17006
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17007
|
+
*/
|
|
17008
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
17009
|
+
const { surfaceOptions, size } = options;
|
|
17010
|
+
const { timeMs } = surfaceOptions;
|
|
17011
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
17012
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
17013
|
+
continue;
|
|
17014
|
+
}
|
|
17015
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
17016
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
17017
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
17018
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
17019
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
17020
|
+
...options,
|
|
17021
|
+
latitude,
|
|
17022
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
17023
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
17024
|
+
});
|
|
17025
|
+
}
|
|
17026
|
+
}
|
|
17027
|
+
}
|
|
17028
|
+
/**
|
|
17029
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
17030
|
+
*
|
|
17031
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17032
|
+
*/
|
|
17033
|
+
function drawBlobbySkinSpots(options) {
|
|
17034
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
17035
|
+
for (const skinSpot of skinSpots) {
|
|
17036
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
17037
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17038
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
17039
|
+
continue;
|
|
17040
|
+
}
|
|
17041
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17042
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
17043
|
+
context.save();
|
|
17044
|
+
context.beginPath();
|
|
17045
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
17046
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
17047
|
+
context.fill();
|
|
17048
|
+
context.restore();
|
|
17049
|
+
}
|
|
17050
|
+
}
|
|
17051
|
+
/**
|
|
17052
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
17053
|
+
*
|
|
17054
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17055
|
+
*/
|
|
17056
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
17057
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
17058
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
17059
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
17060
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
17061
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17062
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
17063
|
+
return;
|
|
17064
|
+
}
|
|
17065
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17066
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17067
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17068
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
17069
|
+
radiusScale *
|
|
17070
|
+
0.78, size * 0.003, size * 0.02);
|
|
17071
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
17072
|
+
radiusScale *
|
|
17073
|
+
0.54, size * 0.0024, size * 0.015);
|
|
17074
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
17075
|
+
context.save();
|
|
17076
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
17077
|
+
context.rotate(rotation);
|
|
17078
|
+
context.beginPath();
|
|
17079
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
17080
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
17081
|
+
context.fill();
|
|
17082
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
17083
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
17084
|
+
context.stroke();
|
|
17085
|
+
context.restore();
|
|
17086
|
+
}
|
|
17087
|
+
/**
|
|
17088
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
17089
|
+
*
|
|
17090
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17091
|
+
*/
|
|
17092
|
+
function drawBlobbyContinuousGloss(options) {
|
|
17093
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
17094
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
17095
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
17096
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
17097
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
17098
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17099
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
17100
|
+
return;
|
|
17101
|
+
}
|
|
17102
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17103
|
+
const glossRadius = size * 0.058;
|
|
17104
|
+
context.save();
|
|
17105
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
17106
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
17107
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
17108
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
17109
|
+
context.fillStyle = glossGradient;
|
|
17110
|
+
context.beginPath();
|
|
17111
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
17112
|
+
context.fill();
|
|
17113
|
+
context.restore();
|
|
17114
|
+
}
|
|
17115
|
+
/**
|
|
17116
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
17117
|
+
*
|
|
17118
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17119
|
+
*/
|
|
17120
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
17121
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
17122
|
+
}
|
|
17123
|
+
/**
|
|
17124
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
17125
|
+
*
|
|
17126
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17127
|
+
*/
|
|
17128
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
17129
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
17130
|
+
return progress * progress * (3 - 2 * progress);
|
|
17131
|
+
}
|
|
17132
|
+
/**
|
|
17133
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
17134
|
+
*
|
|
17135
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17136
|
+
*/
|
|
17137
|
+
function formatAlphaHex(opacity) {
|
|
17138
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
17139
|
+
.toString(16)
|
|
17140
|
+
.padStart(2, '0');
|
|
17141
|
+
}
|
|
17142
|
+
|
|
16388
17143
|
/* eslint-disable no-magic-numbers */
|
|
16389
17144
|
/**
|
|
16390
17145
|
* Octopus avatar visual.
|
|
@@ -17156,6 +17911,7 @@
|
|
|
17156
17911
|
octopus3dAvatarVisual,
|
|
17157
17912
|
octopus3d2AvatarVisual,
|
|
17158
17913
|
octopus3d3AvatarVisual,
|
|
17914
|
+
octopus3d4AvatarVisual,
|
|
17159
17915
|
asciiOctopusAvatarVisual,
|
|
17160
17916
|
minecraftAvatarVisual,
|
|
17161
17917
|
minecraft2AvatarVisual,
|