@promptbook/components 0.112.0-133 → 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 +968 -212
- 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/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +968 -212
- 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/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* @generated
|
|
35
35
|
* @see https://github.com/webgptorg/promptbook
|
|
36
36
|
*/
|
|
37
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
37
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-135';
|
|
38
38
|
/**
|
|
39
39
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
40
40
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -3247,7 +3247,7 @@
|
|
|
3247
3247
|
*
|
|
3248
3248
|
* @private helper of `minecraft2AvatarVisual`
|
|
3249
3249
|
*/
|
|
3250
|
-
const LIGHT_DIRECTION$
|
|
3250
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
3251
3251
|
x: 0.4,
|
|
3252
3252
|
y: -0.65,
|
|
3253
3253
|
z: 0.92,
|
|
@@ -3471,7 +3471,7 @@
|
|
|
3471
3471
|
corners: projectedCorners,
|
|
3472
3472
|
texture: faceDefinition.texture,
|
|
3473
3473
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
3474
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
3474
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
3475
3475
|
outlineColor: cuboid.outlineColor,
|
|
3476
3476
|
};
|
|
3477
3477
|
});
|
|
@@ -4158,7 +4158,7 @@
|
|
|
4158
4158
|
*
|
|
4159
4159
|
* @private helper of `octopus3AvatarVisual`
|
|
4160
4160
|
*/
|
|
4161
|
-
function formatAlphaHex$
|
|
4161
|
+
function formatAlphaHex$2(opacity) {
|
|
4162
4162
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
4163
4163
|
.toString(16)
|
|
4164
4164
|
.padStart(2, '0');
|
|
@@ -4537,7 +4537,7 @@
|
|
|
4537
4537
|
context.beginPath();
|
|
4538
4538
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
4539
4539
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
4540
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
4540
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
4541
4541
|
context.lineWidth = radiusX * 0.08;
|
|
4542
4542
|
context.lineCap = 'round';
|
|
4543
4543
|
context.stroke();
|
|
@@ -4618,7 +4618,7 @@
|
|
|
4618
4618
|
context.beginPath();
|
|
4619
4619
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
4620
4620
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
4621
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
4621
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
4622
4622
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
4623
4623
|
context.lineCap = 'round';
|
|
4624
4624
|
context.stroke();
|
|
@@ -4664,7 +4664,7 @@
|
|
|
4664
4664
|
*
|
|
4665
4665
|
* @private helper of the 3D octopus avatar visuals
|
|
4666
4666
|
*/
|
|
4667
|
-
function formatAlphaHex(opacity) {
|
|
4667
|
+
function formatAlphaHex$1(opacity) {
|
|
4668
4668
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
4669
4669
|
.toString(16)
|
|
4670
4670
|
.padStart(2, '0');
|
|
@@ -4676,7 +4676,7 @@
|
|
|
4676
4676
|
*
|
|
4677
4677
|
* @private helper of `octopus3dAvatarVisual`
|
|
4678
4678
|
*/
|
|
4679
|
-
const LIGHT_DIRECTION$
|
|
4679
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
4680
4680
|
x: 0.48,
|
|
4681
4681
|
y: -0.62,
|
|
4682
4682
|
z: 0.94,
|
|
@@ -4926,7 +4926,7 @@
|
|
|
4926
4926
|
corners: projectedCorners,
|
|
4927
4927
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
4928
4928
|
transformedCorners.length,
|
|
4929
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
4929
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
4930
4930
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
4931
4931
|
outlineColor,
|
|
4932
4932
|
});
|
|
@@ -5115,7 +5115,7 @@
|
|
|
5115
5115
|
*
|
|
5116
5116
|
* @private helper of `octopus3d2AvatarVisual`
|
|
5117
5117
|
*/
|
|
5118
|
-
const LIGHT_DIRECTION$
|
|
5118
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
5119
5119
|
x: 0.38,
|
|
5120
5120
|
y: -0.6,
|
|
5121
5121
|
z: 0.98,
|
|
@@ -5274,13 +5274,13 @@
|
|
|
5274
5274
|
*
|
|
5275
5275
|
* @private helper of `octopus3d2AvatarVisual`
|
|
5276
5276
|
*/
|
|
5277
|
-
const LATITUDE_PATCH_COUNT$
|
|
5277
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
5278
5278
|
/**
|
|
5279
5279
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
5280
5280
|
*
|
|
5281
5281
|
* @private helper of `octopus3d2AvatarVisual`
|
|
5282
5282
|
*/
|
|
5283
|
-
const LONGITUDE_PATCH_COUNT$
|
|
5283
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
5284
5284
|
/**
|
|
5285
5285
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
5286
5286
|
*
|
|
@@ -5294,8 +5294,8 @@
|
|
|
5294
5294
|
*/
|
|
5295
5295
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
5296
5296
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
5297
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
5298
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
5297
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
5298
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
5299
5299
|
const surfacePatches = [];
|
|
5300
5300
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
5301
5301
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -5354,8 +5354,8 @@
|
|
|
5354
5354
|
transformedCorners[2].z +
|
|
5355
5355
|
transformedCorners[3].z) /
|
|
5356
5356
|
4,
|
|
5357
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
5358
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
5357
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
5358
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
5359
5359
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
5360
5360
|
});
|
|
5361
5361
|
}
|
|
@@ -5423,7 +5423,7 @@
|
|
|
5423
5423
|
*
|
|
5424
5424
|
* @private helper of `octopus3d2AvatarVisual`
|
|
5425
5425
|
*/
|
|
5426
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
5426
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
5427
5427
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
5428
5428
|
if (tonalProgress < 0.16) {
|
|
5429
5429
|
return palette.highlight;
|
|
@@ -5469,7 +5469,7 @@
|
|
|
5469
5469
|
*
|
|
5470
5470
|
* @private helper of `octopus3d3AvatarVisual`
|
|
5471
5471
|
*/
|
|
5472
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
5472
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
5473
5473
|
x: 0.34,
|
|
5474
5474
|
y: -0.62,
|
|
5475
5475
|
z: 1,
|
|
@@ -5479,7 +5479,7 @@
|
|
|
5479
5479
|
*
|
|
5480
5480
|
* @private helper of `octopus3d3AvatarVisual`
|
|
5481
5481
|
*/
|
|
5482
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
5482
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
5483
5483
|
/**
|
|
5484
5484
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
5485
5485
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -5492,16 +5492,651 @@
|
|
|
5492
5492
|
* Returns the stable per-avatar state, computing it on first access and returning the cached
|
|
5493
5493
|
* result on every subsequent call within the same `<Avatar/>` mount.
|
|
5494
5494
|
*
|
|
5495
|
-
* @private helper of `octopus3d3AvatarVisual`
|
|
5495
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5496
|
+
*/
|
|
5497
|
+
function getOctopus3d3StableState(createRandom) {
|
|
5498
|
+
const cached = stableStateCache.get(createRandom);
|
|
5499
|
+
if (cached !== undefined) {
|
|
5500
|
+
return cached;
|
|
5501
|
+
}
|
|
5502
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
5503
|
+
const animationRandom = createRandom('octopus3d3-animation-profile');
|
|
5504
|
+
const eyeRandom = createRandom('octopus3d3-eye-profile');
|
|
5505
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
5506
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
5507
|
+
const state = {
|
|
5508
|
+
morphologyProfile,
|
|
5509
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
5510
|
+
leftEyePhaseOffset,
|
|
5511
|
+
rightEyePhaseOffset,
|
|
5512
|
+
tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
5513
|
+
};
|
|
5514
|
+
stableStateCache.set(createRandom, state);
|
|
5515
|
+
return state;
|
|
5516
|
+
}
|
|
5517
|
+
/**
|
|
5518
|
+
* Octopus 3D 3 avatar visual.
|
|
5519
|
+
*
|
|
5520
|
+
* @private built-in avatar visual
|
|
5521
|
+
*/
|
|
5522
|
+
const octopus3d3AvatarVisual = {
|
|
5523
|
+
id: 'octopus3d3',
|
|
5524
|
+
title: 'Octopus 3D 3',
|
|
5525
|
+
description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
|
|
5526
|
+
isAnimated: true,
|
|
5527
|
+
supportsPointerTracking: true,
|
|
5528
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
5529
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
|
|
5530
|
+
const sceneCenterX = size * 0.5;
|
|
5531
|
+
const sceneCenterY = size * 0.535;
|
|
5532
|
+
const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
|
|
5533
|
+
const meshCenter = {
|
|
5534
|
+
x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
5535
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
5536
|
+
z: interaction.intensity * size * 0.018,
|
|
5537
|
+
};
|
|
5538
|
+
const rotationY = -0.1 +
|
|
5539
|
+
Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
|
|
5540
|
+
interaction.bodyOffsetX * 0.22 +
|
|
5541
|
+
interaction.gazeX * 0.88;
|
|
5542
|
+
const rotationX = -0.07 +
|
|
5543
|
+
Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
|
|
5544
|
+
interaction.bodyOffsetY * 0.08 -
|
|
5545
|
+
interaction.gazeY * 0.38;
|
|
5546
|
+
const surfaceOptions = {
|
|
5547
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
|
|
5548
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
|
|
5549
|
+
radiusZ: size *
|
|
5550
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
5551
|
+
(1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
|
|
5552
|
+
morphologyProfile,
|
|
5553
|
+
timeMs,
|
|
5554
|
+
animationPhase,
|
|
5555
|
+
tentacleProfiles,
|
|
5556
|
+
};
|
|
5557
|
+
const surfacePatches = resolveVisibleContinuousOctopusPatches({
|
|
5558
|
+
...surfaceOptions,
|
|
5559
|
+
center: meshCenter,
|
|
5560
|
+
rotationX,
|
|
5561
|
+
rotationY,
|
|
5562
|
+
sceneCenterX,
|
|
5563
|
+
sceneCenterY,
|
|
5564
|
+
size,
|
|
5565
|
+
palette,
|
|
5566
|
+
});
|
|
5567
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
|
|
5568
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
|
|
5569
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
5570
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
5571
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
5572
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
5573
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
|
|
5574
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
|
|
5575
|
+
drawAvatarFrame(context, size, palette);
|
|
5576
|
+
drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
5577
|
+
drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
5578
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
5579
|
+
drawContinuousSurfacePatch(context, surfacePatch);
|
|
5580
|
+
}
|
|
5581
|
+
drawProjectedSurfaceCurrents({
|
|
5582
|
+
context,
|
|
5583
|
+
surfaceOptions,
|
|
5584
|
+
center: meshCenter,
|
|
5585
|
+
rotationX,
|
|
5586
|
+
rotationY,
|
|
5587
|
+
sceneCenterX,
|
|
5588
|
+
sceneCenterY,
|
|
5589
|
+
size,
|
|
5590
|
+
palette,
|
|
5591
|
+
morphologyProfile,
|
|
5592
|
+
timeMs,
|
|
5593
|
+
animationPhase,
|
|
5594
|
+
});
|
|
5595
|
+
drawProjectedTentacleSuckers({
|
|
5596
|
+
context,
|
|
5597
|
+
surfaceOptions,
|
|
5598
|
+
center: meshCenter,
|
|
5599
|
+
rotationX,
|
|
5600
|
+
rotationY,
|
|
5601
|
+
sceneCenterX,
|
|
5602
|
+
sceneCenterY,
|
|
5603
|
+
size,
|
|
5604
|
+
palette,
|
|
5605
|
+
});
|
|
5606
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
5607
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
5608
|
+
drawProjectedOrganicMouth(context, [
|
|
5609
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
5610
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
5611
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
5612
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
5613
|
+
},
|
|
5614
|
+
};
|
|
5615
|
+
/**
|
|
5616
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
5617
|
+
*
|
|
5618
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5619
|
+
*/
|
|
5620
|
+
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
5621
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
5622
|
+
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
5623
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
5624
|
+
return {
|
|
5625
|
+
centerLongitude: -Math.PI * 0.86 +
|
|
5626
|
+
progress * Math.PI * 1.72 +
|
|
5627
|
+
(tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
|
|
5628
|
+
widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
|
|
5629
|
+
lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
|
|
5630
|
+
swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
|
|
5631
|
+
depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
|
|
5632
|
+
phase: tentacleRandom() * Math.PI * 2,
|
|
5633
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
5634
|
+
};
|
|
5635
|
+
});
|
|
5636
|
+
}
|
|
5637
|
+
/**
|
|
5638
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
5639
|
+
*
|
|
5640
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5641
|
+
*/
|
|
5642
|
+
function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
5643
|
+
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.11, sceneCenterY - size * 0.17 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY, size * (0.66 + interaction.intensity * 0.02));
|
|
5644
|
+
glowGradient.addColorStop(0, `${palette.highlight}66`);
|
|
5645
|
+
glowGradient.addColorStop(0.34, `${palette.accent}2e`);
|
|
5646
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
5647
|
+
context.fillStyle = glowGradient;
|
|
5648
|
+
context.fillRect(0, 0, size, size);
|
|
5649
|
+
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1550) * size * 0.05, sceneCenterY + size * 0.29, size * 0.06, sceneCenterX, sceneCenterY + size * 0.3, size * 0.54);
|
|
5650
|
+
lowerGradient.addColorStop(0, `${palette.secondary}25`);
|
|
5651
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
5652
|
+
context.fillStyle = lowerGradient;
|
|
5653
|
+
context.fillRect(0, 0, size, size);
|
|
5654
|
+
}
|
|
5655
|
+
/**
|
|
5656
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
5657
|
+
*
|
|
5658
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
5659
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
5660
|
+
*
|
|
5661
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5662
|
+
*/
|
|
5663
|
+
function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
5664
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
5665
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
5666
|
+
const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
|
|
5667
|
+
const ry = size * 0.06;
|
|
5668
|
+
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
5669
|
+
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
5670
|
+
context.save();
|
|
5671
|
+
context.translate(cx, cy);
|
|
5672
|
+
context.scale(1, ry / rx);
|
|
5673
|
+
const blurRadius = rx * 1.4;
|
|
5674
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
5675
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
5676
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
5677
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
5678
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
5679
|
+
context.fillStyle = shadowGradient;
|
|
5680
|
+
context.beginPath();
|
|
5681
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
5682
|
+
context.fill();
|
|
5683
|
+
context.restore();
|
|
5684
|
+
}
|
|
5685
|
+
/**
|
|
5686
|
+
* Number of latitude segments used by the continuous Octopus 3D 3 mesh.
|
|
5687
|
+
*
|
|
5688
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5689
|
+
*/
|
|
5690
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
5691
|
+
/**
|
|
5692
|
+
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
5693
|
+
*
|
|
5694
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5695
|
+
*/
|
|
5696
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
5697
|
+
/**
|
|
5698
|
+
* Resolves visible projected patches for the continuous octopus mesh.
|
|
5699
|
+
*
|
|
5700
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
5701
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
5702
|
+
* than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
|
|
5703
|
+
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
5704
|
+
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
5705
|
+
*
|
|
5706
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5707
|
+
*/
|
|
5708
|
+
function resolveVisibleContinuousOctopusPatches(options) {
|
|
5709
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
5710
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
5711
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
5712
|
+
const surfacePatches = [];
|
|
5713
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
5714
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
5715
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
5716
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
5717
|
+
}
|
|
5718
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
5719
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
5720
|
+
}
|
|
5721
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
5722
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
5723
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
5724
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
5725
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
5726
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
5727
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
5728
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
|
|
5729
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
|
|
5730
|
+
}
|
|
5731
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
5732
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
5733
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
|
|
5734
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
|
|
5735
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
5736
|
+
}
|
|
5737
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
5738
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
5739
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
5740
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
5741
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
5742
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
5743
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
5744
|
+
const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
5745
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
5749
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
5750
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
5751
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
5752
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
5753
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
5754
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
5755
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
5756
|
+
const transformedCorners = [
|
|
5757
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
5758
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
5759
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
5760
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
5761
|
+
];
|
|
5762
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
5763
|
+
if (surfaceNormal.z <= 0.008) {
|
|
5764
|
+
continue;
|
|
5765
|
+
}
|
|
5766
|
+
const projectedCorners = [
|
|
5767
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
5768
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
5769
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
5770
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
5771
|
+
];
|
|
5772
|
+
surfacePatches.push({
|
|
5773
|
+
corners: projectedCorners,
|
|
5774
|
+
averageDepth: (transformedCorners[0].z +
|
|
5775
|
+
transformedCorners[1].z +
|
|
5776
|
+
transformedCorners[2].z +
|
|
5777
|
+
transformedCorners[3].z) /
|
|
5778
|
+
4,
|
|
5779
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
5780
|
+
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
5781
|
+
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
5782
|
+
});
|
|
5783
|
+
}
|
|
5784
|
+
}
|
|
5785
|
+
return surfacePatches;
|
|
5786
|
+
}
|
|
5787
|
+
/**
|
|
5788
|
+
* Samples one point on the continuous Octopus 3D 3 surface.
|
|
5789
|
+
*
|
|
5790
|
+
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
5791
|
+
* tentacled while still being rendered as one connected blobby mesh.
|
|
5792
|
+
*
|
|
5793
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5794
|
+
*/
|
|
5795
|
+
function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
|
|
5796
|
+
return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
|
|
5797
|
+
}
|
|
5798
|
+
/**
|
|
5799
|
+
* Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
|
|
5800
|
+
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
5801
|
+
*
|
|
5802
|
+
* The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
|
|
5803
|
+
* corner grid, so the same longitude is reused across every latitude row and each
|
|
5804
|
+
* tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
|
|
5805
|
+
*
|
|
5806
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5807
|
+
*/
|
|
5808
|
+
function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
5809
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
5810
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
5811
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
5812
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
5813
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
5814
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
5815
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
5816
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
5817
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
5818
|
+
animationPhase * 0.6 +
|
|
5819
|
+
timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
|
|
5820
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
|
|
5821
|
+
(0.3 + lowerBlend * 0.7);
|
|
5822
|
+
const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
|
|
5823
|
+
lowerBlend *
|
|
5824
|
+
tentacleInfluence.core *
|
|
5825
|
+
tentacleInfluence.swayScale;
|
|
5826
|
+
const horizontalScale = 1.04 +
|
|
5827
|
+
mantleRipple +
|
|
5828
|
+
lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
|
|
5829
|
+
lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
|
|
5830
|
+
upperBlend * 0.08;
|
|
5831
|
+
const depthScale = 1.06 +
|
|
5832
|
+
upperBlend * 0.16 +
|
|
5833
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
|
|
5834
|
+
lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
|
|
5835
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
5836
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
|
|
5837
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
5838
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
|
|
5839
|
+
const lowerDrop = lowerBlend *
|
|
5840
|
+
radiusY *
|
|
5841
|
+
(0.18 +
|
|
5842
|
+
tentacleInfluence.core *
|
|
5843
|
+
(0.38 +
|
|
5844
|
+
tentacleInfluence.lengthScale * 0.22 +
|
|
5845
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
|
|
5846
|
+
return {
|
|
5847
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
|
|
5848
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
|
|
5849
|
+
upperBlend * radiusY * 0.1 +
|
|
5850
|
+
lowerDrop +
|
|
5851
|
+
Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
|
|
5852
|
+
Math.cos(timeMs / 880 + tentacleInfluence.phase) *
|
|
5853
|
+
lowerBlend *
|
|
5854
|
+
tipBlend *
|
|
5855
|
+
tentacleInfluence.core *
|
|
5856
|
+
radiusY *
|
|
5857
|
+
0.034,
|
|
5858
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
5859
|
+
Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
|
|
5860
|
+
lowerBlend *
|
|
5861
|
+
tentacleInfluence.core *
|
|
5862
|
+
radiusZ *
|
|
5863
|
+
0.04,
|
|
5864
|
+
};
|
|
5865
|
+
}
|
|
5866
|
+
/**
|
|
5867
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
5868
|
+
*
|
|
5869
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5870
|
+
*/
|
|
5871
|
+
function resolveContinuousTentacleInfluence(options, longitude) {
|
|
5872
|
+
let totalWeight = 0;
|
|
5873
|
+
let weightedSin = 0;
|
|
5874
|
+
let weightedCos = 0;
|
|
5875
|
+
let weightedWidthScale = 0;
|
|
5876
|
+
let weightedLengthScale = 0;
|
|
5877
|
+
let weightedSwayScale = 0;
|
|
5878
|
+
let weightedDepthScale = 0;
|
|
5879
|
+
let weightedPhase = 0;
|
|
5880
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
5881
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
5882
|
+
const width = 0.2 * tentacleProfile.widthScale;
|
|
5883
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
5884
|
+
totalWeight += weight;
|
|
5885
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
5886
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
5887
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
5888
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
5889
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
5890
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
5891
|
+
weightedPhase += tentacleProfile.phase * weight;
|
|
5892
|
+
}
|
|
5893
|
+
if (totalWeight < 0.0001) {
|
|
5894
|
+
return {
|
|
5895
|
+
core: 0,
|
|
5896
|
+
centerLongitude: longitude,
|
|
5897
|
+
widthScale: 1,
|
|
5898
|
+
lengthScale: 1,
|
|
5899
|
+
swayScale: 1,
|
|
5900
|
+
depthScale: 1,
|
|
5901
|
+
phase: 0,
|
|
5902
|
+
};
|
|
5903
|
+
}
|
|
5904
|
+
return {
|
|
5905
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
5906
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
5907
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
5908
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
5909
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
5910
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
5911
|
+
phase: weightedPhase / totalWeight,
|
|
5912
|
+
};
|
|
5913
|
+
}
|
|
5914
|
+
/**
|
|
5915
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
5916
|
+
*
|
|
5917
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5918
|
+
*/
|
|
5919
|
+
function resolveContinuousLobeWave(options, longitude) {
|
|
5920
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
5921
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
5922
|
+
animationPhase +
|
|
5923
|
+
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
5924
|
+
1) /
|
|
5925
|
+
2);
|
|
5926
|
+
}
|
|
5927
|
+
/**
|
|
5928
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
5929
|
+
*
|
|
5930
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5931
|
+
*/
|
|
5932
|
+
function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
5933
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
5934
|
+
if (tonalProgress < 0.14) {
|
|
5935
|
+
return palette.highlight;
|
|
5936
|
+
}
|
|
5937
|
+
if (tonalProgress < 0.32) {
|
|
5938
|
+
return palette.secondary;
|
|
5939
|
+
}
|
|
5940
|
+
if (tonalProgress < 0.72) {
|
|
5941
|
+
return forwardness > 0.55 ? palette.secondary : palette.primary;
|
|
5942
|
+
}
|
|
5943
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
5944
|
+
}
|
|
5945
|
+
/**
|
|
5946
|
+
* Draws one projected mesh patch with soft shading and a subtle edge.
|
|
5947
|
+
*
|
|
5948
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5949
|
+
*/
|
|
5950
|
+
function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
5951
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
5952
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
5953
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
|
|
5954
|
+
}
|
|
5955
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
5956
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
5957
|
+
}
|
|
5958
|
+
context.save();
|
|
5959
|
+
context.beginPath();
|
|
5960
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
5961
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
5962
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
5963
|
+
}
|
|
5964
|
+
context.closePath();
|
|
5965
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
5966
|
+
context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
|
|
5967
|
+
context.lineJoin = 'round';
|
|
5968
|
+
context.stroke();
|
|
5969
|
+
context.restore();
|
|
5970
|
+
}
|
|
5971
|
+
/**
|
|
5972
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
5973
|
+
*
|
|
5974
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
5975
|
+
*/
|
|
5976
|
+
function drawProjectedSurfaceCurrents(options) {
|
|
5977
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
5978
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
5979
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
5980
|
+
context.save();
|
|
5981
|
+
context.lineCap = 'round';
|
|
5982
|
+
context.lineJoin = 'round';
|
|
5983
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
5984
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
5985
|
+
const projectedPoints = [];
|
|
5986
|
+
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
|
|
5987
|
+
const progress = sampleIndex / 7;
|
|
5988
|
+
const latitude = -0.46 + progress * 0.74;
|
|
5989
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
|
|
5990
|
+
const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
5991
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
5992
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
if (projectedPoints.length < 3) {
|
|
5996
|
+
continue;
|
|
5997
|
+
}
|
|
5998
|
+
context.beginPath();
|
|
5999
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
6000
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
6001
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
6002
|
+
}
|
|
6003
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
6004
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
6005
|
+
context.stroke();
|
|
6006
|
+
}
|
|
6007
|
+
context.restore();
|
|
6008
|
+
}
|
|
6009
|
+
/**
|
|
6010
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
6011
|
+
*
|
|
6012
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
6013
|
+
*/
|
|
6014
|
+
function drawProjectedTentacleSuckers(options) {
|
|
6015
|
+
const { surfaceOptions, size } = options;
|
|
6016
|
+
const { timeMs } = surfaceOptions;
|
|
6017
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
6018
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
|
|
6019
|
+
continue;
|
|
6020
|
+
}
|
|
6021
|
+
for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
|
|
6022
|
+
const latitude = 0.52 + suckerIndex * 0.14;
|
|
6023
|
+
const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
|
|
6024
|
+
const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
|
|
6025
|
+
drawProjectedSurfaceSpot({
|
|
6026
|
+
...options,
|
|
6027
|
+
latitude,
|
|
6028
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
6029
|
+
radiusScale: size * (0.0065 - suckerIndex * 0.0007),
|
|
6030
|
+
});
|
|
6031
|
+
}
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
/**
|
|
6035
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
6036
|
+
*
|
|
6037
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
6038
|
+
*/
|
|
6039
|
+
function drawProjectedSurfaceSpot(options) {
|
|
6040
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
6041
|
+
const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
|
|
6042
|
+
const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
6043
|
+
const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
6044
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
6045
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
6046
|
+
return;
|
|
6047
|
+
}
|
|
6048
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
6049
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
6050
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
6051
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
6052
|
+
radiusScale *
|
|
6053
|
+
0.74, size * 0.003, size * 0.018);
|
|
6054
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
6055
|
+
radiusScale *
|
|
6056
|
+
0.52, size * 0.0024, size * 0.014);
|
|
6057
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
6058
|
+
context.save();
|
|
6059
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
6060
|
+
context.rotate(rotation);
|
|
6061
|
+
context.beginPath();
|
|
6062
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
6063
|
+
context.fillStyle = `${palette.highlight}73`;
|
|
6064
|
+
context.fill();
|
|
6065
|
+
context.strokeStyle = `${palette.highlight}99`;
|
|
6066
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
6067
|
+
context.stroke();
|
|
6068
|
+
context.restore();
|
|
6069
|
+
}
|
|
6070
|
+
/**
|
|
6071
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
6072
|
+
*
|
|
6073
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
6074
|
+
*/
|
|
6075
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
6076
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
6077
|
+
}
|
|
6078
|
+
/**
|
|
6079
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
6080
|
+
*
|
|
6081
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
6082
|
+
*/
|
|
6083
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
6084
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
6085
|
+
return progress * progress * (3 - 2 * progress);
|
|
6086
|
+
}
|
|
6087
|
+
|
|
6088
|
+
/* eslint-disable no-magic-numbers */
|
|
6089
|
+
/**
|
|
6090
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
6091
|
+
*
|
|
6092
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6093
|
+
*/
|
|
6094
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
6095
|
+
x: 0.32,
|
|
6096
|
+
y: -0.66,
|
|
6097
|
+
z: 1,
|
|
6098
|
+
});
|
|
6099
|
+
/**
|
|
6100
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
6101
|
+
*
|
|
6102
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6103
|
+
*/
|
|
6104
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
6105
|
+
x: -0.7,
|
|
6106
|
+
y: -0.18,
|
|
6107
|
+
z: 0.55,
|
|
6108
|
+
});
|
|
6109
|
+
/**
|
|
6110
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
6111
|
+
*
|
|
6112
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6113
|
+
*/
|
|
6114
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
6115
|
+
/**
|
|
6116
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
6117
|
+
*
|
|
6118
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6119
|
+
*/
|
|
6120
|
+
const SKIN_SPOT_COUNT = 14;
|
|
6121
|
+
/**
|
|
6122
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
6123
|
+
*
|
|
6124
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6125
|
+
*/
|
|
6126
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
6127
|
+
/**
|
|
6128
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
6129
|
+
*
|
|
6130
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5496
6131
|
*/
|
|
5497
|
-
function
|
|
5498
|
-
const cached =
|
|
6132
|
+
function getOctopus3d4StableState(createRandom) {
|
|
6133
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
5499
6134
|
if (cached !== undefined) {
|
|
5500
6135
|
return cached;
|
|
5501
6136
|
}
|
|
5502
6137
|
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
5503
|
-
const animationRandom = createRandom('
|
|
5504
|
-
const eyeRandom = createRandom('
|
|
6138
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
6139
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
5505
6140
|
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
5506
6141
|
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
5507
6142
|
const state = {
|
|
@@ -5509,52 +6144,53 @@
|
|
|
5509
6144
|
animationPhase: animationRandom() * Math.PI * 2,
|
|
5510
6145
|
leftEyePhaseOffset,
|
|
5511
6146
|
rightEyePhaseOffset,
|
|
5512
|
-
tentacleProfiles:
|
|
6147
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
6148
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
5513
6149
|
};
|
|
5514
|
-
|
|
6150
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
5515
6151
|
return state;
|
|
5516
6152
|
}
|
|
5517
6153
|
/**
|
|
5518
|
-
* Octopus 3D
|
|
6154
|
+
* Octopus 3D 4 avatar visual.
|
|
5519
6155
|
*
|
|
5520
6156
|
* @private built-in avatar visual
|
|
5521
6157
|
*/
|
|
5522
|
-
const
|
|
5523
|
-
id: '
|
|
5524
|
-
title: 'Octopus 3D
|
|
5525
|
-
description: 'Cute
|
|
6158
|
+
const octopus3d4AvatarVisual = {
|
|
6159
|
+
id: 'octopus3d4',
|
|
6160
|
+
title: 'Octopus 3D 4',
|
|
6161
|
+
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.',
|
|
5526
6162
|
isAnimated: true,
|
|
5527
6163
|
supportsPointerTracking: true,
|
|
5528
6164
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
5529
|
-
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } =
|
|
6165
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
5530
6166
|
const sceneCenterX = size * 0.5;
|
|
5531
6167
|
const sceneCenterY = size * 0.535;
|
|
5532
|
-
const bob = Math.sin(timeMs /
|
|
6168
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
5533
6169
|
const meshCenter = {
|
|
5534
|
-
x: interaction.bodyOffsetX * size * 0.
|
|
5535
|
-
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.
|
|
5536
|
-
z: interaction.intensity * size * 0.
|
|
5537
|
-
};
|
|
5538
|
-
const rotationY = -0.
|
|
5539
|
-
Math.sin(timeMs /
|
|
5540
|
-
interaction.bodyOffsetX * 0.
|
|
5541
|
-
interaction.gazeX * 0.
|
|
6170
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
6171
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
6172
|
+
z: interaction.intensity * size * 0.02,
|
|
6173
|
+
};
|
|
6174
|
+
const rotationY = -0.08 +
|
|
6175
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
6176
|
+
interaction.bodyOffsetX * 0.24 +
|
|
6177
|
+
interaction.gazeX * 0.98;
|
|
5542
6178
|
const rotationX = -0.07 +
|
|
5543
|
-
Math.cos(timeMs /
|
|
6179
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
5544
6180
|
interaction.bodyOffsetY * 0.08 -
|
|
5545
|
-
interaction.gazeY * 0.
|
|
6181
|
+
interaction.gazeY * 0.42;
|
|
5546
6182
|
const surfaceOptions = {
|
|
5547
|
-
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.
|
|
5548
|
-
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.
|
|
6183
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
6184
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
5549
6185
|
radiusZ: size *
|
|
5550
6186
|
morphologyProfile.body.bodyRadiusRatio *
|
|
5551
|
-
(1.
|
|
6187
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
5552
6188
|
morphologyProfile,
|
|
5553
6189
|
timeMs,
|
|
5554
6190
|
animationPhase,
|
|
5555
6191
|
tentacleProfiles,
|
|
5556
6192
|
};
|
|
5557
|
-
const surfacePatches =
|
|
6193
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
5558
6194
|
...surfaceOptions,
|
|
5559
6195
|
center: meshCenter,
|
|
5560
6196
|
rotationX,
|
|
@@ -5564,21 +6200,33 @@
|
|
|
5564
6200
|
size,
|
|
5565
6201
|
palette,
|
|
5566
6202
|
});
|
|
5567
|
-
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.
|
|
5568
|
-
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.
|
|
5569
|
-
const mouthLatitude = clampNumber$1(eyeLatitude + 0.
|
|
6203
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
6204
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
6205
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
5570
6206
|
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
5571
6207
|
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
5572
6208
|
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
5573
|
-
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.
|
|
5574
|
-
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.
|
|
6209
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
6210
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
5575
6211
|
drawAvatarFrame(context, size, palette);
|
|
5576
|
-
|
|
5577
|
-
|
|
6212
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
6213
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
5578
6214
|
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
5579
|
-
|
|
6215
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
5580
6216
|
}
|
|
5581
|
-
|
|
6217
|
+
drawBlobbySkinSpots({
|
|
6218
|
+
context,
|
|
6219
|
+
surfaceOptions,
|
|
6220
|
+
center: meshCenter,
|
|
6221
|
+
rotationX,
|
|
6222
|
+
rotationY,
|
|
6223
|
+
sceneCenterX,
|
|
6224
|
+
sceneCenterY,
|
|
6225
|
+
size,
|
|
6226
|
+
palette,
|
|
6227
|
+
skinSpots,
|
|
6228
|
+
});
|
|
6229
|
+
drawBlobbyContinuousCurrents({
|
|
5582
6230
|
context,
|
|
5583
6231
|
surfaceOptions,
|
|
5584
6232
|
center: meshCenter,
|
|
@@ -5592,7 +6240,7 @@
|
|
|
5592
6240
|
timeMs,
|
|
5593
6241
|
animationPhase,
|
|
5594
6242
|
});
|
|
5595
|
-
|
|
6243
|
+
drawBlobbyContinuousSuckers({
|
|
5596
6244
|
context,
|
|
5597
6245
|
surfaceOptions,
|
|
5598
6246
|
center: meshCenter,
|
|
@@ -5603,51 +6251,78 @@
|
|
|
5603
6251
|
size,
|
|
5604
6252
|
palette,
|
|
5605
6253
|
});
|
|
5606
|
-
|
|
5607
|
-
|
|
6254
|
+
drawBlobbyContinuousGloss({
|
|
6255
|
+
context,
|
|
6256
|
+
surfaceOptions,
|
|
6257
|
+
center: meshCenter,
|
|
6258
|
+
rotationX,
|
|
6259
|
+
rotationY,
|
|
6260
|
+
sceneCenterX,
|
|
6261
|
+
sceneCenterY,
|
|
6262
|
+
size,
|
|
6263
|
+
palette,
|
|
6264
|
+
});
|
|
6265
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
6266
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
5608
6267
|
drawProjectedOrganicMouth(context, [
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
6268
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
6269
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
6270
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
5612
6271
|
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
5613
6272
|
},
|
|
5614
6273
|
};
|
|
5615
6274
|
/**
|
|
5616
6275
|
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
5617
6276
|
*
|
|
5618
|
-
* @private helper of `
|
|
6277
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5619
6278
|
*/
|
|
5620
|
-
function
|
|
6279
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
5621
6280
|
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
5622
|
-
const tentacleRandom = createRandom(`
|
|
6281
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
5623
6282
|
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
5624
6283
|
return {
|
|
5625
|
-
centerLongitude: -Math.PI * 0.
|
|
5626
|
-
progress * Math.PI * 1.
|
|
5627
|
-
(tentacleRandom() - 0.5) * (0.
|
|
5628
|
-
widthScale: 0.
|
|
5629
|
-
lengthScale: 0.
|
|
5630
|
-
swayScale: 0.
|
|
5631
|
-
depthScale: 0.
|
|
5632
|
-
|
|
6284
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
6285
|
+
progress * Math.PI * 1.8 +
|
|
6286
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
6287
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
6288
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
6289
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
6290
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
6291
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
6292
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
6293
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
5633
6294
|
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
5634
6295
|
};
|
|
5635
6296
|
});
|
|
5636
6297
|
}
|
|
6298
|
+
/**
|
|
6299
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
6300
|
+
*
|
|
6301
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6302
|
+
*/
|
|
6303
|
+
function createBlobbySkinSpots(createRandom) {
|
|
6304
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
6305
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
6306
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
6307
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
6308
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
6309
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
6310
|
+
}));
|
|
6311
|
+
}
|
|
5637
6312
|
/**
|
|
5638
6313
|
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
5639
6314
|
*
|
|
5640
|
-
* @private helper of `
|
|
6315
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5641
6316
|
*/
|
|
5642
|
-
function
|
|
5643
|
-
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.
|
|
5644
|
-
glowGradient.addColorStop(0, `${palette.highlight}
|
|
5645
|
-
glowGradient.addColorStop(0.
|
|
6317
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
6318
|
+
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));
|
|
6319
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
6320
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
5646
6321
|
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
5647
6322
|
context.fillStyle = glowGradient;
|
|
5648
6323
|
context.fillRect(0, 0, size, size);
|
|
5649
|
-
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs /
|
|
5650
|
-
lowerGradient.addColorStop(0, `${palette.secondary}
|
|
6324
|
+
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);
|
|
6325
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
5651
6326
|
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
5652
6327
|
context.fillStyle = lowerGradient;
|
|
5653
6328
|
context.fillRect(0, 0, size, size);
|
|
@@ -5655,26 +6330,21 @@
|
|
|
5655
6330
|
/**
|
|
5656
6331
|
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
5657
6332
|
*
|
|
5658
|
-
*
|
|
5659
|
-
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
5660
|
-
*
|
|
5661
|
-
* @private helper of `octopus3d3AvatarVisual`
|
|
6333
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5662
6334
|
*/
|
|
5663
|
-
function
|
|
5664
|
-
const cx = size * 0.5 + interaction.gazeX * size * 0.
|
|
6335
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
6336
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
5665
6337
|
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
5666
|
-
const rx = size * (0.
|
|
5667
|
-
const ry = size * 0.
|
|
5668
|
-
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
5669
|
-
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
6338
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
6339
|
+
const ry = size * 0.062;
|
|
5670
6340
|
context.save();
|
|
5671
6341
|
context.translate(cx, cy);
|
|
5672
6342
|
context.scale(1, ry / rx);
|
|
5673
|
-
const blurRadius = rx * 1.
|
|
6343
|
+
const blurRadius = rx * 1.42;
|
|
5674
6344
|
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
5675
|
-
shadowGradient.addColorStop(0, `${palette.shadow}
|
|
5676
|
-
shadowGradient.addColorStop(0.45, `${palette.shadow}
|
|
5677
|
-
shadowGradient.addColorStop(0.8, `${palette.shadow}
|
|
6345
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
6346
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
6347
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
5678
6348
|
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
5679
6349
|
context.fillStyle = shadowGradient;
|
|
5680
6350
|
context.beginPath();
|
|
@@ -5683,29 +6353,27 @@
|
|
|
5683
6353
|
context.restore();
|
|
5684
6354
|
}
|
|
5685
6355
|
/**
|
|
5686
|
-
* Number of latitude segments used by the continuous Octopus 3D
|
|
6356
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
5687
6357
|
*
|
|
5688
|
-
* @private helper of `
|
|
6358
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5689
6359
|
*/
|
|
5690
|
-
const LATITUDE_PATCH_COUNT =
|
|
6360
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
5691
6361
|
/**
|
|
5692
|
-
* Number of longitude segments used by the continuous Octopus 3D
|
|
6362
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
5693
6363
|
*
|
|
5694
|
-
* @private helper of `
|
|
6364
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5695
6365
|
*/
|
|
5696
|
-
const LONGITUDE_PATCH_COUNT =
|
|
6366
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
5697
6367
|
/**
|
|
5698
|
-
* Resolves visible projected patches for the continuous
|
|
6368
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
5699
6369
|
*
|
|
5700
6370
|
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
5701
6371
|
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
5702
|
-
* than re-evaluated for every patch corner
|
|
5703
|
-
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
5704
|
-
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
6372
|
+
* than re-evaluated for every patch corner.
|
|
5705
6373
|
*
|
|
5706
|
-
* @private helper of `
|
|
6374
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5707
6375
|
*/
|
|
5708
|
-
function
|
|
6376
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
5709
6377
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
5710
6378
|
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
5711
6379
|
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
@@ -5725,13 +6393,13 @@
|
|
|
5725
6393
|
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
5726
6394
|
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
5727
6395
|
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
5728
|
-
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] =
|
|
5729
|
-
cachedLobeWavesByCornerLongitude[boundaryIndex] =
|
|
6396
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
6397
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
5730
6398
|
}
|
|
5731
6399
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
5732
6400
|
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
5733
|
-
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] =
|
|
5734
|
-
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] =
|
|
6401
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
6402
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
5735
6403
|
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
5736
6404
|
}
|
|
5737
6405
|
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
@@ -5741,7 +6409,7 @@
|
|
|
5741
6409
|
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
5742
6410
|
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
5743
6411
|
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
5744
|
-
const cornerSample =
|
|
6412
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
5745
6413
|
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
5746
6414
|
}
|
|
5747
6415
|
}
|
|
@@ -5760,7 +6428,7 @@
|
|
|
5760
6428
|
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
5761
6429
|
];
|
|
5762
6430
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
5763
|
-
if (surfaceNormal.z <= 0.
|
|
6431
|
+
if (surfaceNormal.z <= 0.006) {
|
|
5764
6432
|
continue;
|
|
5765
6433
|
}
|
|
5766
6434
|
const projectedCorners = [
|
|
@@ -5769,6 +6437,8 @@
|
|
|
5769
6437
|
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
5770
6438
|
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
5771
6439
|
];
|
|
6440
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
6441
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
5772
6442
|
surfacePatches.push({
|
|
5773
6443
|
corners: projectedCorners,
|
|
5774
6444
|
averageDepth: (transformedCorners[0].z +
|
|
@@ -5776,99 +6446,113 @@
|
|
|
5776
6446
|
transformedCorners[2].z +
|
|
5777
6447
|
transformedCorners[3].z) /
|
|
5778
6448
|
4,
|
|
5779
|
-
lightIntensity
|
|
5780
|
-
|
|
5781
|
-
|
|
6449
|
+
lightIntensity,
|
|
6450
|
+
rimLightIntensity,
|
|
6451
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
6452
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
5782
6453
|
});
|
|
5783
6454
|
}
|
|
5784
6455
|
}
|
|
5785
6456
|
return surfacePatches;
|
|
5786
6457
|
}
|
|
5787
6458
|
/**
|
|
5788
|
-
* Samples one point on the continuous Octopus 3D
|
|
5789
|
-
*
|
|
5790
|
-
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
5791
|
-
* tentacled while still being rendered as one connected blobby mesh.
|
|
6459
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
5792
6460
|
*
|
|
5793
|
-
* @private helper of `
|
|
6461
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5794
6462
|
*/
|
|
5795
|
-
function
|
|
5796
|
-
return
|
|
6463
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
6464
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
5797
6465
|
}
|
|
5798
6466
|
/**
|
|
5799
|
-
* Samples one point on the continuous Octopus 3D
|
|
5800
|
-
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
6467
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
5801
6468
|
*
|
|
5802
|
-
* The patch loop quantizes the mesh into a fixed
|
|
5803
|
-
*
|
|
5804
|
-
*
|
|
6469
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
6470
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
6471
|
+
* of `latitudePatchCount * 4` times.
|
|
5805
6472
|
*
|
|
5806
|
-
* @private helper of `
|
|
6473
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5807
6474
|
*/
|
|
5808
|
-
function
|
|
6475
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
5809
6476
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
5810
6477
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
5811
6478
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
5812
|
-
const upperBlend = Math.pow(1 - verticalProgress, 1.
|
|
5813
|
-
const lowerBlend = smoothStep(0.
|
|
5814
|
-
const tipBlend = smoothStep(0.
|
|
6479
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
6480
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
6481
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
5815
6482
|
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
5816
|
-
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.
|
|
6483
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
5817
6484
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
5818
6485
|
animationPhase * 0.6 +
|
|
5819
|
-
timeMs / (
|
|
5820
|
-
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.
|
|
5821
|
-
(0.
|
|
5822
|
-
const
|
|
6486
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
6487
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
6488
|
+
(0.32 + lowerBlend * 0.7);
|
|
6489
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
5823
6490
|
lowerBlend *
|
|
5824
6491
|
tentacleInfluence.core *
|
|
5825
6492
|
tentacleInfluence.swayScale;
|
|
5826
|
-
const
|
|
6493
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
6494
|
+
lowerBlend *
|
|
6495
|
+
tentacleInfluence.core *
|
|
6496
|
+
tentacleInfluence.swayScale *
|
|
6497
|
+
0.42;
|
|
6498
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
6499
|
+
tipBlend *
|
|
6500
|
+
tentacleInfluence.core *
|
|
6501
|
+
tentacleInfluence.curlScale *
|
|
6502
|
+
0.36;
|
|
6503
|
+
const horizontalScale = 1.06 +
|
|
5827
6504
|
mantleRipple +
|
|
5828
|
-
lowerBlend * (0.
|
|
5829
|
-
lowerBlend * tentacleInfluence.core * (0.
|
|
6505
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
6506
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
5830
6507
|
upperBlend * 0.08;
|
|
5831
|
-
const depthScale = 1.
|
|
6508
|
+
const depthScale = 1.08 +
|
|
5832
6509
|
upperBlend * 0.16 +
|
|
5833
|
-
Math.max(0, Math.cos(effectiveLongitude)) * 0.
|
|
5834
|
-
lowerBlend * tentacleInfluence.core * (0.
|
|
6510
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
6511
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
5835
6512
|
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
5836
|
-
const tentacleTubeRadius = lowerBlend *
|
|
6513
|
+
const tentacleTubeRadius = lowerBlend *
|
|
6514
|
+
tentacleInfluence.core *
|
|
6515
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
6516
|
+
radiusX;
|
|
5837
6517
|
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
5838
|
-
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.
|
|
6518
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
5839
6519
|
const lowerDrop = lowerBlend *
|
|
5840
6520
|
radiusY *
|
|
5841
|
-
(0.
|
|
6521
|
+
(0.2 +
|
|
5842
6522
|
tentacleInfluence.core *
|
|
5843
|
-
(0.
|
|
5844
|
-
tentacleInfluence.lengthScale * 0.
|
|
5845
|
-
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.
|
|
6523
|
+
(0.42 +
|
|
6524
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
6525
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
6526
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
5846
6527
|
return {
|
|
5847
|
-
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
5848
|
-
|
|
5849
|
-
|
|
6528
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
6529
|
+
combinedTentacleSway +
|
|
6530
|
+
tentacleCurl * radiusX * 0.18,
|
|
6531
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
6532
|
+
upperBlend * radiusY * 0.12 +
|
|
5850
6533
|
lowerDrop +
|
|
5851
|
-
Math.sin(timeMs /
|
|
5852
|
-
Math.cos(timeMs /
|
|
6534
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
6535
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
5853
6536
|
lowerBlend *
|
|
5854
6537
|
tipBlend *
|
|
5855
6538
|
tentacleInfluence.core *
|
|
5856
6539
|
radiusY *
|
|
5857
|
-
0.
|
|
6540
|
+
0.04,
|
|
5858
6541
|
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
5859
|
-
Math.cos(timeMs /
|
|
6542
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
5860
6543
|
lowerBlend *
|
|
5861
6544
|
tentacleInfluence.core *
|
|
5862
6545
|
radiusZ *
|
|
5863
|
-
0.
|
|
6546
|
+
0.044 +
|
|
6547
|
+
tentacleCurl * radiusZ * 0.14,
|
|
5864
6548
|
};
|
|
5865
6549
|
}
|
|
5866
6550
|
/**
|
|
5867
6551
|
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
5868
6552
|
*
|
|
5869
|
-
* @private helper of `
|
|
6553
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5870
6554
|
*/
|
|
5871
|
-
function
|
|
6555
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
5872
6556
|
let totalWeight = 0;
|
|
5873
6557
|
let weightedSin = 0;
|
|
5874
6558
|
let weightedCos = 0;
|
|
@@ -5876,10 +6560,12 @@
|
|
|
5876
6560
|
let weightedLengthScale = 0;
|
|
5877
6561
|
let weightedSwayScale = 0;
|
|
5878
6562
|
let weightedDepthScale = 0;
|
|
5879
|
-
let
|
|
6563
|
+
let weightedCurlScale = 0;
|
|
6564
|
+
let weightedPrimaryPhase = 0;
|
|
6565
|
+
let weightedSecondaryPhase = 0;
|
|
5880
6566
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
5881
6567
|
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
5882
|
-
const width = 0.
|
|
6568
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
5883
6569
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
5884
6570
|
totalWeight += weight;
|
|
5885
6571
|
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
@@ -5888,7 +6574,9 @@
|
|
|
5888
6574
|
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
5889
6575
|
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
5890
6576
|
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
5891
|
-
|
|
6577
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
6578
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
6579
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
5892
6580
|
}
|
|
5893
6581
|
if (totalWeight < 0.0001) {
|
|
5894
6582
|
return {
|
|
@@ -5898,7 +6586,9 @@
|
|
|
5898
6586
|
lengthScale: 1,
|
|
5899
6587
|
swayScale: 1,
|
|
5900
6588
|
depthScale: 1,
|
|
5901
|
-
|
|
6589
|
+
curlScale: 1,
|
|
6590
|
+
primaryPhase: 0,
|
|
6591
|
+
secondaryPhase: 0,
|
|
5902
6592
|
};
|
|
5903
6593
|
}
|
|
5904
6594
|
return {
|
|
@@ -5908,52 +6598,57 @@
|
|
|
5908
6598
|
lengthScale: weightedLengthScale / totalWeight,
|
|
5909
6599
|
swayScale: weightedSwayScale / totalWeight,
|
|
5910
6600
|
depthScale: weightedDepthScale / totalWeight,
|
|
5911
|
-
|
|
6601
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
6602
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
6603
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
5912
6604
|
};
|
|
5913
6605
|
}
|
|
5914
6606
|
/**
|
|
5915
6607
|
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
5916
6608
|
*
|
|
5917
|
-
* @private helper of `
|
|
6609
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5918
6610
|
*/
|
|
5919
|
-
function
|
|
6611
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
5920
6612
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
5921
6613
|
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
5922
6614
|
animationPhase +
|
|
5923
|
-
timeMs / (
|
|
6615
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
5924
6616
|
1) /
|
|
5925
6617
|
2);
|
|
5926
6618
|
}
|
|
5927
6619
|
/**
|
|
5928
6620
|
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
5929
6621
|
*
|
|
5930
|
-
* @private helper of `
|
|
6622
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5931
6623
|
*/
|
|
5932
|
-
function
|
|
6624
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
5933
6625
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
5934
|
-
if (tonalProgress < 0.
|
|
6626
|
+
if (tonalProgress < 0.12) {
|
|
5935
6627
|
return palette.highlight;
|
|
5936
6628
|
}
|
|
5937
|
-
if (tonalProgress < 0.
|
|
6629
|
+
if (tonalProgress < 0.3) {
|
|
5938
6630
|
return palette.secondary;
|
|
5939
6631
|
}
|
|
5940
6632
|
if (tonalProgress < 0.72) {
|
|
5941
|
-
return forwardness > 0.
|
|
6633
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
5942
6634
|
}
|
|
5943
6635
|
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
5944
6636
|
}
|
|
5945
6637
|
/**
|
|
5946
|
-
* Draws one projected mesh patch with
|
|
6638
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
5947
6639
|
*
|
|
5948
|
-
* @private helper of `
|
|
6640
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5949
6641
|
*/
|
|
5950
|
-
function
|
|
6642
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
5951
6643
|
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
5952
6644
|
if (surfacePatch.lightIntensity > 0) {
|
|
5953
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.
|
|
6645
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
5954
6646
|
}
|
|
5955
6647
|
else if (surfacePatch.lightIntensity < 0) {
|
|
5956
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.
|
|
6648
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
6649
|
+
}
|
|
6650
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
6651
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
5957
6652
|
}
|
|
5958
6653
|
context.save();
|
|
5959
6654
|
context.beginPath();
|
|
@@ -5963,7 +6658,7 @@
|
|
|
5963
6658
|
}
|
|
5964
6659
|
context.closePath();
|
|
5965
6660
|
context.strokeStyle = surfacePatch.outlineColor;
|
|
5966
|
-
context.lineWidth = Math.max(0.
|
|
6661
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
5967
6662
|
context.lineJoin = 'round';
|
|
5968
6663
|
context.stroke();
|
|
5969
6664
|
context.restore();
|
|
@@ -5971,9 +6666,9 @@
|
|
|
5971
6666
|
/**
|
|
5972
6667
|
* Draws projected mantle-current lines on the front of the mesh.
|
|
5973
6668
|
*
|
|
5974
|
-
* @private helper of `
|
|
6669
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
5975
6670
|
*/
|
|
5976
|
-
function
|
|
6671
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
5977
6672
|
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
5978
6673
|
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
5979
6674
|
const centerIndex = (currentCount - 1) / 2;
|
|
@@ -5983,11 +6678,11 @@
|
|
|
5983
6678
|
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
5984
6679
|
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
5985
6680
|
const projectedPoints = [];
|
|
5986
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
5987
|
-
const progress = sampleIndex /
|
|
5988
|
-
const latitude = -0.
|
|
5989
|
-
const longitude = baseLongitude + Math.sin(timeMs /
|
|
5990
|
-
const scenePoint = transformScenePoint(
|
|
6681
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
6682
|
+
const progress = sampleIndex / 8;
|
|
6683
|
+
const latitude = -0.48 + progress * 0.78;
|
|
6684
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
6685
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
5991
6686
|
if (scenePoint.z > center.z - size * 0.016) {
|
|
5992
6687
|
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
5993
6688
|
}
|
|
@@ -6009,38 +6704,61 @@
|
|
|
6009
6704
|
/**
|
|
6010
6705
|
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
6011
6706
|
*
|
|
6012
|
-
* @private helper of `
|
|
6707
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6013
6708
|
*/
|
|
6014
|
-
function
|
|
6709
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
6015
6710
|
const { surfaceOptions, size } = options;
|
|
6016
6711
|
const { timeMs } = surfaceOptions;
|
|
6017
6712
|
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
6018
|
-
if (Math.cos(tentacleProfile.centerLongitude) < -0.
|
|
6713
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
6019
6714
|
continue;
|
|
6020
6715
|
}
|
|
6021
|
-
for (let suckerIndex = 0; suckerIndex <
|
|
6022
|
-
const latitude = 0.
|
|
6023
|
-
const sideOffset = tentacleProfile.suckerSide * (0.
|
|
6024
|
-
const waveOffset = Math.sin(timeMs /
|
|
6025
|
-
|
|
6716
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
6717
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
6718
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
6719
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
6720
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
6026
6721
|
...options,
|
|
6027
6722
|
latitude,
|
|
6028
6723
|
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
6029
|
-
radiusScale: size * (0.
|
|
6724
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
6030
6725
|
});
|
|
6031
6726
|
}
|
|
6032
6727
|
}
|
|
6033
6728
|
}
|
|
6729
|
+
/**
|
|
6730
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
6731
|
+
*
|
|
6732
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6733
|
+
*/
|
|
6734
|
+
function drawBlobbySkinSpots(options) {
|
|
6735
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
6736
|
+
for (const skinSpot of skinSpots) {
|
|
6737
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
6738
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
6739
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
6740
|
+
continue;
|
|
6741
|
+
}
|
|
6742
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
6743
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
6744
|
+
context.save();
|
|
6745
|
+
context.beginPath();
|
|
6746
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
6747
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
6748
|
+
context.fill();
|
|
6749
|
+
context.restore();
|
|
6750
|
+
}
|
|
6751
|
+
}
|
|
6034
6752
|
/**
|
|
6035
6753
|
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
6036
6754
|
*
|
|
6037
|
-
* @private helper of `
|
|
6755
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6038
6756
|
*/
|
|
6039
|
-
function
|
|
6757
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
6040
6758
|
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
6041
|
-
const localCenter =
|
|
6042
|
-
const localHorizontal =
|
|
6043
|
-
const localVertical =
|
|
6759
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
6760
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
6761
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
6044
6762
|
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
6045
6763
|
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
6046
6764
|
return;
|
|
@@ -6050,27 +6768,55 @@
|
|
|
6050
6768
|
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
6051
6769
|
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
6052
6770
|
radiusScale *
|
|
6053
|
-
0.
|
|
6771
|
+
0.78, size * 0.003, size * 0.02);
|
|
6054
6772
|
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
6055
6773
|
radiusScale *
|
|
6056
|
-
0.
|
|
6774
|
+
0.54, size * 0.0024, size * 0.015);
|
|
6057
6775
|
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
6058
6776
|
context.save();
|
|
6059
6777
|
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
6060
6778
|
context.rotate(rotation);
|
|
6061
6779
|
context.beginPath();
|
|
6062
6780
|
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
6063
|
-
context.fillStyle = `${palette.highlight}
|
|
6781
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
6064
6782
|
context.fill();
|
|
6065
|
-
context.strokeStyle = `${palette.highlight}
|
|
6783
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
6066
6784
|
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
6067
6785
|
context.stroke();
|
|
6068
6786
|
context.restore();
|
|
6069
6787
|
}
|
|
6788
|
+
/**
|
|
6789
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
6790
|
+
*
|
|
6791
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6792
|
+
*/
|
|
6793
|
+
function drawBlobbyContinuousGloss(options) {
|
|
6794
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
6795
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
6796
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
6797
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
6798
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
6799
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
6800
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
6801
|
+
return;
|
|
6802
|
+
}
|
|
6803
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
6804
|
+
const glossRadius = size * 0.058;
|
|
6805
|
+
context.save();
|
|
6806
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
6807
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
6808
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
6809
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
6810
|
+
context.fillStyle = glossGradient;
|
|
6811
|
+
context.beginPath();
|
|
6812
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
6813
|
+
context.fill();
|
|
6814
|
+
context.restore();
|
|
6815
|
+
}
|
|
6070
6816
|
/**
|
|
6071
6817
|
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
6072
6818
|
*
|
|
6073
|
-
* @private helper of `
|
|
6819
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6074
6820
|
*/
|
|
6075
6821
|
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
6076
6822
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
@@ -6078,12 +6824,22 @@
|
|
|
6078
6824
|
/**
|
|
6079
6825
|
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
6080
6826
|
*
|
|
6081
|
-
* @private helper of `
|
|
6827
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6082
6828
|
*/
|
|
6083
6829
|
function smoothStep(edgeStart, edgeEnd, value) {
|
|
6084
6830
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
6085
6831
|
return progress * progress * (3 - 2 * progress);
|
|
6086
6832
|
}
|
|
6833
|
+
/**
|
|
6834
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
6835
|
+
*
|
|
6836
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
6837
|
+
*/
|
|
6838
|
+
function formatAlphaHex(opacity) {
|
|
6839
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
6840
|
+
.toString(16)
|
|
6841
|
+
.padStart(2, '0');
|
|
6842
|
+
}
|
|
6087
6843
|
|
|
6088
6844
|
/* eslint-disable no-magic-numbers */
|
|
6089
6845
|
/**
|
|
@@ -6856,6 +7612,7 @@
|
|
|
6856
7612
|
octopus3dAvatarVisual,
|
|
6857
7613
|
octopus3d2AvatarVisual,
|
|
6858
7614
|
octopus3d3AvatarVisual,
|
|
7615
|
+
octopus3d4AvatarVisual,
|
|
6859
7616
|
asciiOctopusAvatarVisual,
|
|
6860
7617
|
minecraftAvatarVisual,
|
|
6861
7618
|
minecraft2AvatarVisual,
|
|
@@ -6937,7 +7694,7 @@
|
|
|
6937
7694
|
*
|
|
6938
7695
|
* @private shared avatar contract
|
|
6939
7696
|
*/
|
|
6940
|
-
const DEFAULT_AGENT_AVATAR_VISUAL_ID = '
|
|
7697
|
+
const DEFAULT_AGENT_AVATAR_VISUAL_ID = 'octopus3d4';
|
|
6941
7698
|
/**
|
|
6942
7699
|
* Resolves the avatar visual preferred by an agent, then falls back to a federated server default
|
|
6943
7700
|
* and finally to the caller/server default.
|
|
@@ -9308,6 +10065,11 @@
|
|
|
9308
10065
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
9309
10066
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
9310
10067
|
*
|
|
10068
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
10069
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
10070
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
10071
|
+
* outbound request.
|
|
10072
|
+
*
|
|
9311
10073
|
* @public exported from `@promptbook/utils`
|
|
9312
10074
|
*/
|
|
9313
10075
|
function isValidAgentUrl(url) {
|
|
@@ -9321,12 +10083,6 @@
|
|
|
9321
10083
|
// TODO: [🐠]
|
|
9322
10084
|
return false;
|
|
9323
10085
|
}
|
|
9324
|
-
/*
|
|
9325
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
9326
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
9327
|
-
return false;
|
|
9328
|
-
}
|
|
9329
|
-
*/
|
|
9330
10086
|
return true;
|
|
9331
10087
|
}
|
|
9332
10088
|
// TODO: [🐠] Maybe more info why the URL is invalid
|