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