@promptbook/cli 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/apps/agents-server/src/app/admin/chat-feedback/page.tsx +2 -4
- package/apps/agents-server/src/app/admin/chat-history/page.tsx +2 -4
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +28 -9
- package/apps/agents-server/src/app/api/chat/route.ts +29 -7
- package/apps/agents-server/src/app/api/chat-feedback/export/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-feedback/route.ts +3 -3
- package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-history/export/route.ts +2 -2
- package/apps/agents-server/src/app/api/chat-history/route.ts +3 -3
- package/apps/agents-server/src/app/api/chat-streaming/route.ts +29 -0
- package/apps/agents-server/src/app/api/elevenlabs/tts/route.ts +60 -2
- package/apps/agents-server/src/app/api/images/[filename]/route.ts +102 -0
- package/apps/agents-server/src/app/api/openai/v1/audio/transcriptions/route.ts +111 -2
- package/apps/agents-server/src/app/api/page-preview/check/route.ts +18 -0
- package/apps/agents-server/src/app/api/page-preview/screenshot/route.ts +15 -8
- package/apps/agents-server/src/app/api/team-agent-profile/route.ts +20 -0
- package/apps/agents-server/src/components/AgentContextMenu/useAgentContextMenuItems.ts +27 -19
- package/apps/agents-server/src/constants/defaultAgentAvatarVisual.ts +1 -1
- package/apps/agents-server/src/database/migrations/2026-06-2700-default-agent-avatar-visual-octopus3d4.sql +16 -0
- package/apps/agents-server/src/utils/agentOwnership.ts +54 -5
- package/apps/agents-server/src/utils/findAgentForCallerWriteAccess.ts +36 -0
- package/apps/agents-server/src/utils/paidApiRequestGuard.ts +241 -0
- package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +36 -8
- package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +11 -2
- package/apps/agents-server/src/utils/userChat/resolveUserChatProgressToolHighlights.ts +100 -0
- package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +4 -3
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +272 -27
- package/apps/agents-server/src/utils/validateApiKey.ts +7 -3
- 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/src/avatars/types/AvatarVisualDefinition.ts +1 -0
- package/src/avatars/visuals/avatarVisualRegistry.ts +2 -0
- package/src/avatars/visuals/octopus3d4AvatarVisual.ts +1295 -0
- package/src/other/templates/getTemplatesPipelineCollection.ts +772 -942
- package/src/utils/agents/resolveAgentAvatarImageUrl.ts +1 -1
- package/src/utils/validators/url/isValidAgentUrl.ts +5 -7
- package/src/version.ts +1 -1
- package/src/versions.txt +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/esm/index.es.js
CHANGED
|
@@ -59,7 +59,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
59
59
|
* @generated
|
|
60
60
|
* @see https://github.com/webgptorg/promptbook
|
|
61
61
|
*/
|
|
62
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
62
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-135';
|
|
63
63
|
/**
|
|
64
64
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
65
65
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -7735,6 +7735,11 @@ function isVoidPseudoAgentReference(rawReference) {
|
|
|
7735
7735
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
7736
7736
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
7737
7737
|
*
|
|
7738
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
7739
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
7740
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
7741
|
+
* outbound request.
|
|
7742
|
+
*
|
|
7738
7743
|
* @public exported from `@promptbook/utils`
|
|
7739
7744
|
*/
|
|
7740
7745
|
function isValidAgentUrl(url) {
|
|
@@ -7748,12 +7753,6 @@ function isValidAgentUrl(url) {
|
|
|
7748
7753
|
// TODO: [🐠]
|
|
7749
7754
|
return false;
|
|
7750
7755
|
}
|
|
7751
|
-
/*
|
|
7752
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
7753
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
7754
|
-
return false;
|
|
7755
|
-
}
|
|
7756
|
-
*/
|
|
7757
7756
|
return true;
|
|
7758
7757
|
}
|
|
7759
7758
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -11402,7 +11401,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
|
|
|
11402
11401
|
*
|
|
11403
11402
|
* @private helper of `minecraft2AvatarVisual`
|
|
11404
11403
|
*/
|
|
11405
|
-
const LIGHT_DIRECTION$
|
|
11404
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
11406
11405
|
x: 0.4,
|
|
11407
11406
|
y: -0.65,
|
|
11408
11407
|
z: 0.92,
|
|
@@ -11626,7 +11625,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
|
|
|
11626
11625
|
corners: projectedCorners,
|
|
11627
11626
|
texture: faceDefinition.texture,
|
|
11628
11627
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
11629
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
11628
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
11630
11629
|
outlineColor: cuboid.outlineColor,
|
|
11631
11630
|
};
|
|
11632
11631
|
});
|
|
@@ -12313,7 +12312,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
|
|
|
12313
12312
|
*
|
|
12314
12313
|
* @private helper of `octopus3AvatarVisual`
|
|
12315
12314
|
*/
|
|
12316
|
-
function formatAlphaHex$
|
|
12315
|
+
function formatAlphaHex$2(opacity) {
|
|
12317
12316
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
12318
12317
|
.toString(16)
|
|
12319
12318
|
.padStart(2, '0');
|
|
@@ -12692,7 +12691,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
|
|
|
12692
12691
|
context.beginPath();
|
|
12693
12692
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
12694
12693
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
12695
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
12694
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
12696
12695
|
context.lineWidth = radiusX * 0.08;
|
|
12697
12696
|
context.lineCap = 'round';
|
|
12698
12697
|
context.stroke();
|
|
@@ -12773,7 +12772,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
|
|
|
12773
12772
|
context.beginPath();
|
|
12774
12773
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
12775
12774
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
12776
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
12775
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
12777
12776
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
12778
12777
|
context.lineCap = 'round';
|
|
12779
12778
|
context.stroke();
|
|
@@ -12819,7 +12818,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
|
|
|
12819
12818
|
*
|
|
12820
12819
|
* @private helper of the 3D octopus avatar visuals
|
|
12821
12820
|
*/
|
|
12822
|
-
function formatAlphaHex(opacity) {
|
|
12821
|
+
function formatAlphaHex$1(opacity) {
|
|
12823
12822
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
12824
12823
|
.toString(16)
|
|
12825
12824
|
.padStart(2, '0');
|
|
@@ -12831,7 +12830,7 @@ function formatAlphaHex(opacity) {
|
|
|
12831
12830
|
*
|
|
12832
12831
|
* @private helper of `octopus3dAvatarVisual`
|
|
12833
12832
|
*/
|
|
12834
|
-
const LIGHT_DIRECTION$
|
|
12833
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
12835
12834
|
x: 0.48,
|
|
12836
12835
|
y: -0.62,
|
|
12837
12836
|
z: 0.94,
|
|
@@ -13081,7 +13080,7 @@ function resolveVisibleEllipsoidPatches(options) {
|
|
|
13081
13080
|
corners: projectedCorners,
|
|
13082
13081
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
13083
13082
|
transformedCorners.length,
|
|
13084
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
13083
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
13085
13084
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
13086
13085
|
outlineColor,
|
|
13087
13086
|
});
|
|
@@ -13270,7 +13269,7 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
|
|
|
13270
13269
|
*
|
|
13271
13270
|
* @private helper of `octopus3d2AvatarVisual`
|
|
13272
13271
|
*/
|
|
13273
|
-
const LIGHT_DIRECTION$
|
|
13272
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
13274
13273
|
x: 0.38,
|
|
13275
13274
|
y: -0.6,
|
|
13276
13275
|
z: 0.98,
|
|
@@ -13429,13 +13428,13 @@ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, mo
|
|
|
13429
13428
|
*
|
|
13430
13429
|
* @private helper of `octopus3d2AvatarVisual`
|
|
13431
13430
|
*/
|
|
13432
|
-
const LATITUDE_PATCH_COUNT$
|
|
13431
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
13433
13432
|
/**
|
|
13434
13433
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
13435
13434
|
*
|
|
13436
13435
|
* @private helper of `octopus3d2AvatarVisual`
|
|
13437
13436
|
*/
|
|
13438
|
-
const LONGITUDE_PATCH_COUNT$
|
|
13437
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
13439
13438
|
/**
|
|
13440
13439
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
13441
13440
|
*
|
|
@@ -13449,8 +13448,8 @@ const LONGITUDE_PATCH_COUNT$1 = 24;
|
|
|
13449
13448
|
*/
|
|
13450
13449
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
13451
13450
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
13452
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
13453
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
13451
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
13452
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
13454
13453
|
const surfacePatches = [];
|
|
13455
13454
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
13456
13455
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -13509,8 +13508,8 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
13509
13508
|
transformedCorners[2].z +
|
|
13510
13509
|
transformedCorners[3].z) /
|
|
13511
13510
|
4,
|
|
13512
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
13513
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
13511
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
13512
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
13514
13513
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
13515
13514
|
});
|
|
13516
13515
|
}
|
|
@@ -13578,7 +13577,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
|
|
|
13578
13577
|
*
|
|
13579
13578
|
* @private helper of `octopus3d2AvatarVisual`
|
|
13580
13579
|
*/
|
|
13581
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
13580
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
13582
13581
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
13583
13582
|
if (tonalProgress < 0.16) {
|
|
13584
13583
|
return palette.highlight;
|
|
@@ -13624,7 +13623,7 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
|
|
|
13624
13623
|
*
|
|
13625
13624
|
* @private helper of `octopus3d3AvatarVisual`
|
|
13626
13625
|
*/
|
|
13627
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
13626
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
13628
13627
|
x: 0.34,
|
|
13629
13628
|
y: -0.62,
|
|
13630
13629
|
z: 1,
|
|
@@ -13634,7 +13633,7 @@ const LIGHT_DIRECTION = normalizeVector3({
|
|
|
13634
13633
|
*
|
|
13635
13634
|
* @private helper of `octopus3d3AvatarVisual`
|
|
13636
13635
|
*/
|
|
13637
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
13636
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
13638
13637
|
/**
|
|
13639
13638
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
13640
13639
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -13773,9 +13772,9 @@ const octopus3d3AvatarVisual = {
|
|
|
13773
13772
|
* @private helper of `octopus3d3AvatarVisual`
|
|
13774
13773
|
*/
|
|
13775
13774
|
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
13776
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
13775
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
13777
13776
|
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
13778
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
13777
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
13779
13778
|
return {
|
|
13780
13779
|
centerLongitude: -Math.PI * 0.86 +
|
|
13781
13780
|
progress * Math.PI * 1.72 +
|
|
@@ -13842,13 +13841,13 @@ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs
|
|
|
13842
13841
|
*
|
|
13843
13842
|
* @private helper of `octopus3d3AvatarVisual`
|
|
13844
13843
|
*/
|
|
13845
|
-
const LATITUDE_PATCH_COUNT = 16;
|
|
13844
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
13846
13845
|
/**
|
|
13847
13846
|
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
13848
13847
|
*
|
|
13849
13848
|
* @private helper of `octopus3d3AvatarVisual`
|
|
13850
13849
|
*/
|
|
13851
|
-
const LONGITUDE_PATCH_COUNT = 40;
|
|
13850
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
13852
13851
|
/**
|
|
13853
13852
|
* Resolves visible projected patches for the continuous octopus mesh.
|
|
13854
13853
|
*
|
|
@@ -13862,8 +13861,8 @@ const LONGITUDE_PATCH_COUNT = 40;
|
|
|
13862
13861
|
*/
|
|
13863
13862
|
function resolveVisibleContinuousOctopusPatches(options) {
|
|
13864
13863
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
13865
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
13866
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
13864
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
13865
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
13867
13866
|
const surfacePatches = [];
|
|
13868
13867
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
13869
13868
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -13931,7 +13930,7 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
13931
13930
|
transformedCorners[2].z +
|
|
13932
13931
|
transformedCorners[3].z) /
|
|
13933
13932
|
4,
|
|
13934
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
13933
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
13935
13934
|
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
13936
13935
|
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
13937
13936
|
});
|
|
@@ -13965,9 +13964,9 @@ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude
|
|
|
13965
13964
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
13966
13965
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
13967
13966
|
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
13968
|
-
const lowerBlend = smoothStep(0.38, 1, verticalProgress);
|
|
13969
|
-
const tipBlend = smoothStep(0.68, 1, verticalProgress);
|
|
13970
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
13967
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
13968
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
13969
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
13971
13970
|
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
13972
13971
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
13973
13972
|
animationPhase * 0.6 +
|
|
@@ -14033,7 +14032,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
14033
14032
|
let weightedDepthScale = 0;
|
|
14034
14033
|
let weightedPhase = 0;
|
|
14035
14034
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
14036
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
14035
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
14037
14036
|
const width = 0.2 * tentacleProfile.widthScale;
|
|
14038
14037
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
14039
14038
|
totalWeight += weight;
|
|
@@ -14073,7 +14072,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
14073
14072
|
*/
|
|
14074
14073
|
function resolveContinuousLobeWave(options, longitude) {
|
|
14075
14074
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
14076
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
14075
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
14077
14076
|
animationPhase +
|
|
14078
14077
|
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
14079
14078
|
1) /
|
|
@@ -14227,7 +14226,7 @@ function drawProjectedSurfaceSpot(options) {
|
|
|
14227
14226
|
*
|
|
14228
14227
|
* @private helper of `octopus3d3AvatarVisual`
|
|
14229
14228
|
*/
|
|
14230
|
-
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
14229
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
14231
14230
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
14232
14231
|
}
|
|
14233
14232
|
/**
|
|
@@ -14235,11 +14234,767 @@ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
|
14235
14234
|
*
|
|
14236
14235
|
* @private helper of `octopus3d3AvatarVisual`
|
|
14237
14236
|
*/
|
|
14238
|
-
function smoothStep(edgeStart, edgeEnd, value) {
|
|
14237
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
14239
14238
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
14240
14239
|
return progress * progress * (3 - 2 * progress);
|
|
14241
14240
|
}
|
|
14242
14241
|
|
|
14242
|
+
/* eslint-disable no-magic-numbers */
|
|
14243
|
+
/**
|
|
14244
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
14245
|
+
*
|
|
14246
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14247
|
+
*/
|
|
14248
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
14249
|
+
x: 0.32,
|
|
14250
|
+
y: -0.66,
|
|
14251
|
+
z: 1,
|
|
14252
|
+
});
|
|
14253
|
+
/**
|
|
14254
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
14255
|
+
*
|
|
14256
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14257
|
+
*/
|
|
14258
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
14259
|
+
x: -0.7,
|
|
14260
|
+
y: -0.18,
|
|
14261
|
+
z: 0.55,
|
|
14262
|
+
});
|
|
14263
|
+
/**
|
|
14264
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
14265
|
+
*
|
|
14266
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14267
|
+
*/
|
|
14268
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
14269
|
+
/**
|
|
14270
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
14271
|
+
*
|
|
14272
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14273
|
+
*/
|
|
14274
|
+
const SKIN_SPOT_COUNT = 14;
|
|
14275
|
+
/**
|
|
14276
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
14277
|
+
*
|
|
14278
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14279
|
+
*/
|
|
14280
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
14281
|
+
/**
|
|
14282
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
14283
|
+
*
|
|
14284
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14285
|
+
*/
|
|
14286
|
+
function getOctopus3d4StableState(createRandom) {
|
|
14287
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
14288
|
+
if (cached !== undefined) {
|
|
14289
|
+
return cached;
|
|
14290
|
+
}
|
|
14291
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
14292
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
14293
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
14294
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
14295
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
14296
|
+
const state = {
|
|
14297
|
+
morphologyProfile,
|
|
14298
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
14299
|
+
leftEyePhaseOffset,
|
|
14300
|
+
rightEyePhaseOffset,
|
|
14301
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
14302
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
14303
|
+
};
|
|
14304
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
14305
|
+
return state;
|
|
14306
|
+
}
|
|
14307
|
+
/**
|
|
14308
|
+
* Octopus 3D 4 avatar visual.
|
|
14309
|
+
*
|
|
14310
|
+
* @private built-in avatar visual
|
|
14311
|
+
*/
|
|
14312
|
+
const octopus3d4AvatarVisual = {
|
|
14313
|
+
id: 'octopus3d4',
|
|
14314
|
+
title: 'Octopus 3D 4',
|
|
14315
|
+
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.',
|
|
14316
|
+
isAnimated: true,
|
|
14317
|
+
supportsPointerTracking: true,
|
|
14318
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
14319
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
14320
|
+
const sceneCenterX = size * 0.5;
|
|
14321
|
+
const sceneCenterY = size * 0.535;
|
|
14322
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
14323
|
+
const meshCenter = {
|
|
14324
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
14325
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
14326
|
+
z: interaction.intensity * size * 0.02,
|
|
14327
|
+
};
|
|
14328
|
+
const rotationY = -0.08 +
|
|
14329
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
14330
|
+
interaction.bodyOffsetX * 0.24 +
|
|
14331
|
+
interaction.gazeX * 0.98;
|
|
14332
|
+
const rotationX = -0.07 +
|
|
14333
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
14334
|
+
interaction.bodyOffsetY * 0.08 -
|
|
14335
|
+
interaction.gazeY * 0.42;
|
|
14336
|
+
const surfaceOptions = {
|
|
14337
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
14338
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
14339
|
+
radiusZ: size *
|
|
14340
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
14341
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
14342
|
+
morphologyProfile,
|
|
14343
|
+
timeMs,
|
|
14344
|
+
animationPhase,
|
|
14345
|
+
tentacleProfiles,
|
|
14346
|
+
};
|
|
14347
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
14348
|
+
...surfaceOptions,
|
|
14349
|
+
center: meshCenter,
|
|
14350
|
+
rotationX,
|
|
14351
|
+
rotationY,
|
|
14352
|
+
sceneCenterX,
|
|
14353
|
+
sceneCenterY,
|
|
14354
|
+
size,
|
|
14355
|
+
palette,
|
|
14356
|
+
});
|
|
14357
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
14358
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
14359
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
14360
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
14361
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
14362
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
14363
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
14364
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
14365
|
+
drawAvatarFrame(context, size, palette);
|
|
14366
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
14367
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
14368
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
14369
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
14370
|
+
}
|
|
14371
|
+
drawBlobbySkinSpots({
|
|
14372
|
+
context,
|
|
14373
|
+
surfaceOptions,
|
|
14374
|
+
center: meshCenter,
|
|
14375
|
+
rotationX,
|
|
14376
|
+
rotationY,
|
|
14377
|
+
sceneCenterX,
|
|
14378
|
+
sceneCenterY,
|
|
14379
|
+
size,
|
|
14380
|
+
palette,
|
|
14381
|
+
skinSpots,
|
|
14382
|
+
});
|
|
14383
|
+
drawBlobbyContinuousCurrents({
|
|
14384
|
+
context,
|
|
14385
|
+
surfaceOptions,
|
|
14386
|
+
center: meshCenter,
|
|
14387
|
+
rotationX,
|
|
14388
|
+
rotationY,
|
|
14389
|
+
sceneCenterX,
|
|
14390
|
+
sceneCenterY,
|
|
14391
|
+
size,
|
|
14392
|
+
palette,
|
|
14393
|
+
morphologyProfile,
|
|
14394
|
+
timeMs,
|
|
14395
|
+
animationPhase,
|
|
14396
|
+
});
|
|
14397
|
+
drawBlobbyContinuousSuckers({
|
|
14398
|
+
context,
|
|
14399
|
+
surfaceOptions,
|
|
14400
|
+
center: meshCenter,
|
|
14401
|
+
rotationX,
|
|
14402
|
+
rotationY,
|
|
14403
|
+
sceneCenterX,
|
|
14404
|
+
sceneCenterY,
|
|
14405
|
+
size,
|
|
14406
|
+
palette,
|
|
14407
|
+
});
|
|
14408
|
+
drawBlobbyContinuousGloss({
|
|
14409
|
+
context,
|
|
14410
|
+
surfaceOptions,
|
|
14411
|
+
center: meshCenter,
|
|
14412
|
+
rotationX,
|
|
14413
|
+
rotationY,
|
|
14414
|
+
sceneCenterX,
|
|
14415
|
+
sceneCenterY,
|
|
14416
|
+
size,
|
|
14417
|
+
palette,
|
|
14418
|
+
});
|
|
14419
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
14420
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
14421
|
+
drawProjectedOrganicMouth(context, [
|
|
14422
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
14423
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
14424
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
14425
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
14426
|
+
},
|
|
14427
|
+
};
|
|
14428
|
+
/**
|
|
14429
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
14430
|
+
*
|
|
14431
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14432
|
+
*/
|
|
14433
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
14434
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
14435
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
14436
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
14437
|
+
return {
|
|
14438
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
14439
|
+
progress * Math.PI * 1.8 +
|
|
14440
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
14441
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
14442
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
14443
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
14444
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
14445
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
14446
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
14447
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
14448
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
14449
|
+
};
|
|
14450
|
+
});
|
|
14451
|
+
}
|
|
14452
|
+
/**
|
|
14453
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
14454
|
+
*
|
|
14455
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14456
|
+
*/
|
|
14457
|
+
function createBlobbySkinSpots(createRandom) {
|
|
14458
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
14459
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
14460
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
14461
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
14462
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
14463
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
14464
|
+
}));
|
|
14465
|
+
}
|
|
14466
|
+
/**
|
|
14467
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
14468
|
+
*
|
|
14469
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14470
|
+
*/
|
|
14471
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
14472
|
+
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));
|
|
14473
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
14474
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
14475
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
14476
|
+
context.fillStyle = glowGradient;
|
|
14477
|
+
context.fillRect(0, 0, size, size);
|
|
14478
|
+
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);
|
|
14479
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
14480
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
14481
|
+
context.fillStyle = lowerGradient;
|
|
14482
|
+
context.fillRect(0, 0, size, size);
|
|
14483
|
+
}
|
|
14484
|
+
/**
|
|
14485
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
14486
|
+
*
|
|
14487
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14488
|
+
*/
|
|
14489
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
14490
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
14491
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
14492
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
14493
|
+
const ry = size * 0.062;
|
|
14494
|
+
context.save();
|
|
14495
|
+
context.translate(cx, cy);
|
|
14496
|
+
context.scale(1, ry / rx);
|
|
14497
|
+
const blurRadius = rx * 1.42;
|
|
14498
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
14499
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
14500
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
14501
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
14502
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
14503
|
+
context.fillStyle = shadowGradient;
|
|
14504
|
+
context.beginPath();
|
|
14505
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
14506
|
+
context.fill();
|
|
14507
|
+
context.restore();
|
|
14508
|
+
}
|
|
14509
|
+
/**
|
|
14510
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
14511
|
+
*
|
|
14512
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14513
|
+
*/
|
|
14514
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
14515
|
+
/**
|
|
14516
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
14517
|
+
*
|
|
14518
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14519
|
+
*/
|
|
14520
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
14521
|
+
/**
|
|
14522
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
14523
|
+
*
|
|
14524
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
14525
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
14526
|
+
* than re-evaluated for every patch corner.
|
|
14527
|
+
*
|
|
14528
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14529
|
+
*/
|
|
14530
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
14531
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
14532
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
14533
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
14534
|
+
const surfacePatches = [];
|
|
14535
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
14536
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
14537
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
14538
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
14539
|
+
}
|
|
14540
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
14541
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
14542
|
+
}
|
|
14543
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
14544
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
14545
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
14546
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
14547
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
14548
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
14549
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
14550
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
14551
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
14552
|
+
}
|
|
14553
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
14554
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
14555
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
14556
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
14557
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
14558
|
+
}
|
|
14559
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
14560
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
14561
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
14562
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
14563
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
14564
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
14565
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
14566
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
14567
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
14568
|
+
}
|
|
14569
|
+
}
|
|
14570
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
14571
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
14572
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
14573
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
14574
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
14575
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
14576
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
14577
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
14578
|
+
const transformedCorners = [
|
|
14579
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
14580
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
14581
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
14582
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
14583
|
+
];
|
|
14584
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
14585
|
+
if (surfaceNormal.z <= 0.006) {
|
|
14586
|
+
continue;
|
|
14587
|
+
}
|
|
14588
|
+
const projectedCorners = [
|
|
14589
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
14590
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
14591
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
14592
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
14593
|
+
];
|
|
14594
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
14595
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
14596
|
+
surfacePatches.push({
|
|
14597
|
+
corners: projectedCorners,
|
|
14598
|
+
averageDepth: (transformedCorners[0].z +
|
|
14599
|
+
transformedCorners[1].z +
|
|
14600
|
+
transformedCorners[2].z +
|
|
14601
|
+
transformedCorners[3].z) /
|
|
14602
|
+
4,
|
|
14603
|
+
lightIntensity,
|
|
14604
|
+
rimLightIntensity,
|
|
14605
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
14606
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
14607
|
+
});
|
|
14608
|
+
}
|
|
14609
|
+
}
|
|
14610
|
+
return surfacePatches;
|
|
14611
|
+
}
|
|
14612
|
+
/**
|
|
14613
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
14614
|
+
*
|
|
14615
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14616
|
+
*/
|
|
14617
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
14618
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
14619
|
+
}
|
|
14620
|
+
/**
|
|
14621
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
14622
|
+
*
|
|
14623
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
14624
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
14625
|
+
* of `latitudePatchCount * 4` times.
|
|
14626
|
+
*
|
|
14627
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14628
|
+
*/
|
|
14629
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
14630
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
14631
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
14632
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
14633
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
14634
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
14635
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
14636
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
14637
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
14638
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
14639
|
+
animationPhase * 0.6 +
|
|
14640
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
14641
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
14642
|
+
(0.32 + lowerBlend * 0.7);
|
|
14643
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
14644
|
+
lowerBlend *
|
|
14645
|
+
tentacleInfluence.core *
|
|
14646
|
+
tentacleInfluence.swayScale;
|
|
14647
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
14648
|
+
lowerBlend *
|
|
14649
|
+
tentacleInfluence.core *
|
|
14650
|
+
tentacleInfluence.swayScale *
|
|
14651
|
+
0.42;
|
|
14652
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
14653
|
+
tipBlend *
|
|
14654
|
+
tentacleInfluence.core *
|
|
14655
|
+
tentacleInfluence.curlScale *
|
|
14656
|
+
0.36;
|
|
14657
|
+
const horizontalScale = 1.06 +
|
|
14658
|
+
mantleRipple +
|
|
14659
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
14660
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
14661
|
+
upperBlend * 0.08;
|
|
14662
|
+
const depthScale = 1.08 +
|
|
14663
|
+
upperBlend * 0.16 +
|
|
14664
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
14665
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
14666
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
14667
|
+
const tentacleTubeRadius = lowerBlend *
|
|
14668
|
+
tentacleInfluence.core *
|
|
14669
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
14670
|
+
radiusX;
|
|
14671
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
14672
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
14673
|
+
const lowerDrop = lowerBlend *
|
|
14674
|
+
radiusY *
|
|
14675
|
+
(0.2 +
|
|
14676
|
+
tentacleInfluence.core *
|
|
14677
|
+
(0.42 +
|
|
14678
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
14679
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
14680
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
14681
|
+
return {
|
|
14682
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
14683
|
+
combinedTentacleSway +
|
|
14684
|
+
tentacleCurl * radiusX * 0.18,
|
|
14685
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
14686
|
+
upperBlend * radiusY * 0.12 +
|
|
14687
|
+
lowerDrop +
|
|
14688
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
14689
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
14690
|
+
lowerBlend *
|
|
14691
|
+
tipBlend *
|
|
14692
|
+
tentacleInfluence.core *
|
|
14693
|
+
radiusY *
|
|
14694
|
+
0.04,
|
|
14695
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
14696
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
14697
|
+
lowerBlend *
|
|
14698
|
+
tentacleInfluence.core *
|
|
14699
|
+
radiusZ *
|
|
14700
|
+
0.044 +
|
|
14701
|
+
tentacleCurl * radiusZ * 0.14,
|
|
14702
|
+
};
|
|
14703
|
+
}
|
|
14704
|
+
/**
|
|
14705
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
14706
|
+
*
|
|
14707
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14708
|
+
*/
|
|
14709
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
14710
|
+
let totalWeight = 0;
|
|
14711
|
+
let weightedSin = 0;
|
|
14712
|
+
let weightedCos = 0;
|
|
14713
|
+
let weightedWidthScale = 0;
|
|
14714
|
+
let weightedLengthScale = 0;
|
|
14715
|
+
let weightedSwayScale = 0;
|
|
14716
|
+
let weightedDepthScale = 0;
|
|
14717
|
+
let weightedCurlScale = 0;
|
|
14718
|
+
let weightedPrimaryPhase = 0;
|
|
14719
|
+
let weightedSecondaryPhase = 0;
|
|
14720
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
14721
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
14722
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
14723
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
14724
|
+
totalWeight += weight;
|
|
14725
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
14726
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
14727
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
14728
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
14729
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
14730
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
14731
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
14732
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
14733
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
14734
|
+
}
|
|
14735
|
+
if (totalWeight < 0.0001) {
|
|
14736
|
+
return {
|
|
14737
|
+
core: 0,
|
|
14738
|
+
centerLongitude: longitude,
|
|
14739
|
+
widthScale: 1,
|
|
14740
|
+
lengthScale: 1,
|
|
14741
|
+
swayScale: 1,
|
|
14742
|
+
depthScale: 1,
|
|
14743
|
+
curlScale: 1,
|
|
14744
|
+
primaryPhase: 0,
|
|
14745
|
+
secondaryPhase: 0,
|
|
14746
|
+
};
|
|
14747
|
+
}
|
|
14748
|
+
return {
|
|
14749
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
14750
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
14751
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
14752
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
14753
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
14754
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
14755
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
14756
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
14757
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
14758
|
+
};
|
|
14759
|
+
}
|
|
14760
|
+
/**
|
|
14761
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
14762
|
+
*
|
|
14763
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14764
|
+
*/
|
|
14765
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
14766
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
14767
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
14768
|
+
animationPhase +
|
|
14769
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
14770
|
+
1) /
|
|
14771
|
+
2);
|
|
14772
|
+
}
|
|
14773
|
+
/**
|
|
14774
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
14775
|
+
*
|
|
14776
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14777
|
+
*/
|
|
14778
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
14779
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
14780
|
+
if (tonalProgress < 0.12) {
|
|
14781
|
+
return palette.highlight;
|
|
14782
|
+
}
|
|
14783
|
+
if (tonalProgress < 0.3) {
|
|
14784
|
+
return palette.secondary;
|
|
14785
|
+
}
|
|
14786
|
+
if (tonalProgress < 0.72) {
|
|
14787
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
14788
|
+
}
|
|
14789
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
14790
|
+
}
|
|
14791
|
+
/**
|
|
14792
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
14793
|
+
*
|
|
14794
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14795
|
+
*/
|
|
14796
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
14797
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
14798
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
14799
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
14800
|
+
}
|
|
14801
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
14802
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
14803
|
+
}
|
|
14804
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
14805
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
14806
|
+
}
|
|
14807
|
+
context.save();
|
|
14808
|
+
context.beginPath();
|
|
14809
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
14810
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
14811
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
14812
|
+
}
|
|
14813
|
+
context.closePath();
|
|
14814
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
14815
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
14816
|
+
context.lineJoin = 'round';
|
|
14817
|
+
context.stroke();
|
|
14818
|
+
context.restore();
|
|
14819
|
+
}
|
|
14820
|
+
/**
|
|
14821
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
14822
|
+
*
|
|
14823
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14824
|
+
*/
|
|
14825
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
14826
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
14827
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
14828
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
14829
|
+
context.save();
|
|
14830
|
+
context.lineCap = 'round';
|
|
14831
|
+
context.lineJoin = 'round';
|
|
14832
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
14833
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
14834
|
+
const projectedPoints = [];
|
|
14835
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
14836
|
+
const progress = sampleIndex / 8;
|
|
14837
|
+
const latitude = -0.48 + progress * 0.78;
|
|
14838
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
14839
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
14840
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
14841
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
14842
|
+
}
|
|
14843
|
+
}
|
|
14844
|
+
if (projectedPoints.length < 3) {
|
|
14845
|
+
continue;
|
|
14846
|
+
}
|
|
14847
|
+
context.beginPath();
|
|
14848
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
14849
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
14850
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
14851
|
+
}
|
|
14852
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
14853
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
14854
|
+
context.stroke();
|
|
14855
|
+
}
|
|
14856
|
+
context.restore();
|
|
14857
|
+
}
|
|
14858
|
+
/**
|
|
14859
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
14860
|
+
*
|
|
14861
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14862
|
+
*/
|
|
14863
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
14864
|
+
const { surfaceOptions, size } = options;
|
|
14865
|
+
const { timeMs } = surfaceOptions;
|
|
14866
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
14867
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
14868
|
+
continue;
|
|
14869
|
+
}
|
|
14870
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
14871
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
14872
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
14873
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
14874
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
14875
|
+
...options,
|
|
14876
|
+
latitude,
|
|
14877
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
14878
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
14879
|
+
});
|
|
14880
|
+
}
|
|
14881
|
+
}
|
|
14882
|
+
}
|
|
14883
|
+
/**
|
|
14884
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
14885
|
+
*
|
|
14886
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14887
|
+
*/
|
|
14888
|
+
function drawBlobbySkinSpots(options) {
|
|
14889
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
14890
|
+
for (const skinSpot of skinSpots) {
|
|
14891
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
14892
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
14893
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
14894
|
+
continue;
|
|
14895
|
+
}
|
|
14896
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
14897
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
14898
|
+
context.save();
|
|
14899
|
+
context.beginPath();
|
|
14900
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
14901
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
14902
|
+
context.fill();
|
|
14903
|
+
context.restore();
|
|
14904
|
+
}
|
|
14905
|
+
}
|
|
14906
|
+
/**
|
|
14907
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
14908
|
+
*
|
|
14909
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14910
|
+
*/
|
|
14911
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
14912
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
14913
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
14914
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
14915
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
14916
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
14917
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
14918
|
+
return;
|
|
14919
|
+
}
|
|
14920
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
14921
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
14922
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
14923
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
14924
|
+
radiusScale *
|
|
14925
|
+
0.78, size * 0.003, size * 0.02);
|
|
14926
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
14927
|
+
radiusScale *
|
|
14928
|
+
0.54, size * 0.0024, size * 0.015);
|
|
14929
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
14930
|
+
context.save();
|
|
14931
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
14932
|
+
context.rotate(rotation);
|
|
14933
|
+
context.beginPath();
|
|
14934
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
14935
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
14936
|
+
context.fill();
|
|
14937
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
14938
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
14939
|
+
context.stroke();
|
|
14940
|
+
context.restore();
|
|
14941
|
+
}
|
|
14942
|
+
/**
|
|
14943
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
14944
|
+
*
|
|
14945
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14946
|
+
*/
|
|
14947
|
+
function drawBlobbyContinuousGloss(options) {
|
|
14948
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
14949
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
14950
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
14951
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
14952
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
14953
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
14954
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
14955
|
+
return;
|
|
14956
|
+
}
|
|
14957
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
14958
|
+
const glossRadius = size * 0.058;
|
|
14959
|
+
context.save();
|
|
14960
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
14961
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
14962
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
14963
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
14964
|
+
context.fillStyle = glossGradient;
|
|
14965
|
+
context.beginPath();
|
|
14966
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
14967
|
+
context.fill();
|
|
14968
|
+
context.restore();
|
|
14969
|
+
}
|
|
14970
|
+
/**
|
|
14971
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
14972
|
+
*
|
|
14973
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14974
|
+
*/
|
|
14975
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
14976
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
14977
|
+
}
|
|
14978
|
+
/**
|
|
14979
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
14980
|
+
*
|
|
14981
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14982
|
+
*/
|
|
14983
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
14984
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
14985
|
+
return progress * progress * (3 - 2 * progress);
|
|
14986
|
+
}
|
|
14987
|
+
/**
|
|
14988
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
14989
|
+
*
|
|
14990
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
14991
|
+
*/
|
|
14992
|
+
function formatAlphaHex(opacity) {
|
|
14993
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
14994
|
+
.toString(16)
|
|
14995
|
+
.padStart(2, '0');
|
|
14996
|
+
}
|
|
14997
|
+
|
|
14243
14998
|
/* eslint-disable no-magic-numbers */
|
|
14244
14999
|
/**
|
|
14245
15000
|
* Octopus avatar visual.
|
|
@@ -15011,6 +15766,7 @@ const AVATAR_VISUALS = [
|
|
|
15011
15766
|
octopus3dAvatarVisual,
|
|
15012
15767
|
octopus3d2AvatarVisual,
|
|
15013
15768
|
octopus3d3AvatarVisual,
|
|
15769
|
+
octopus3d4AvatarVisual,
|
|
15014
15770
|
asciiOctopusAvatarVisual,
|
|
15015
15771
|
minecraftAvatarVisual,
|
|
15016
15772
|
minecraft2AvatarVisual,
|