@promptbook/browser 0.112.0-133 → 0.112.0-135
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/index.es.js +795 -39
- package/esm/index.es.js.map +1 -1
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +795 -39
- package/umd/index.umd.js.map +1 -1
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @generated
|
|
30
30
|
* @see https://github.com/webgptorg/promptbook
|
|
31
31
|
*/
|
|
32
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
32
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-135';
|
|
33
33
|
/**
|
|
34
34
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
35
35
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -2605,6 +2605,11 @@
|
|
|
2605
2605
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
2606
2606
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
2607
2607
|
*
|
|
2608
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
2609
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
2610
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
2611
|
+
* outbound request.
|
|
2612
|
+
*
|
|
2608
2613
|
* @public exported from `@promptbook/utils`
|
|
2609
2614
|
*/
|
|
2610
2615
|
function isValidAgentUrl(url) {
|
|
@@ -2618,12 +2623,6 @@
|
|
|
2618
2623
|
// TODO: [🐠]
|
|
2619
2624
|
return false;
|
|
2620
2625
|
}
|
|
2621
|
-
/*
|
|
2622
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
2623
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
2624
|
-
return false;
|
|
2625
|
-
}
|
|
2626
|
-
*/
|
|
2627
2626
|
return true;
|
|
2628
2627
|
}
|
|
2629
2628
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -6644,7 +6643,7 @@
|
|
|
6644
6643
|
*
|
|
6645
6644
|
* @private helper of `minecraft2AvatarVisual`
|
|
6646
6645
|
*/
|
|
6647
|
-
const LIGHT_DIRECTION$
|
|
6646
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
6648
6647
|
x: 0.4,
|
|
6649
6648
|
y: -0.65,
|
|
6650
6649
|
z: 0.92,
|
|
@@ -6868,7 +6867,7 @@
|
|
|
6868
6867
|
corners: projectedCorners,
|
|
6869
6868
|
texture: faceDefinition.texture,
|
|
6870
6869
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
6871
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
6870
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
6872
6871
|
outlineColor: cuboid.outlineColor,
|
|
6873
6872
|
};
|
|
6874
6873
|
});
|
|
@@ -7555,7 +7554,7 @@
|
|
|
7555
7554
|
*
|
|
7556
7555
|
* @private helper of `octopus3AvatarVisual`
|
|
7557
7556
|
*/
|
|
7558
|
-
function formatAlphaHex$
|
|
7557
|
+
function formatAlphaHex$2(opacity) {
|
|
7559
7558
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
7560
7559
|
.toString(16)
|
|
7561
7560
|
.padStart(2, '0');
|
|
@@ -7934,7 +7933,7 @@
|
|
|
7934
7933
|
context.beginPath();
|
|
7935
7934
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
7936
7935
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
7937
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
7936
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
7938
7937
|
context.lineWidth = radiusX * 0.08;
|
|
7939
7938
|
context.lineCap = 'round';
|
|
7940
7939
|
context.stroke();
|
|
@@ -8015,7 +8014,7 @@
|
|
|
8015
8014
|
context.beginPath();
|
|
8016
8015
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
8017
8016
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
8018
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
8017
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
8019
8018
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
8020
8019
|
context.lineCap = 'round';
|
|
8021
8020
|
context.stroke();
|
|
@@ -8061,7 +8060,7 @@
|
|
|
8061
8060
|
*
|
|
8062
8061
|
* @private helper of the 3D octopus avatar visuals
|
|
8063
8062
|
*/
|
|
8064
|
-
function formatAlphaHex(opacity) {
|
|
8063
|
+
function formatAlphaHex$1(opacity) {
|
|
8065
8064
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
8066
8065
|
.toString(16)
|
|
8067
8066
|
.padStart(2, '0');
|
|
@@ -8073,7 +8072,7 @@
|
|
|
8073
8072
|
*
|
|
8074
8073
|
* @private helper of `octopus3dAvatarVisual`
|
|
8075
8074
|
*/
|
|
8076
|
-
const LIGHT_DIRECTION$
|
|
8075
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
8077
8076
|
x: 0.48,
|
|
8078
8077
|
y: -0.62,
|
|
8079
8078
|
z: 0.94,
|
|
@@ -8323,7 +8322,7 @@
|
|
|
8323
8322
|
corners: projectedCorners,
|
|
8324
8323
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
8325
8324
|
transformedCorners.length,
|
|
8326
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
8325
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
8327
8326
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
8328
8327
|
outlineColor,
|
|
8329
8328
|
});
|
|
@@ -8512,7 +8511,7 @@
|
|
|
8512
8511
|
*
|
|
8513
8512
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8514
8513
|
*/
|
|
8515
|
-
const LIGHT_DIRECTION$
|
|
8514
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
8516
8515
|
x: 0.38,
|
|
8517
8516
|
y: -0.6,
|
|
8518
8517
|
z: 0.98,
|
|
@@ -8671,13 +8670,13 @@
|
|
|
8671
8670
|
*
|
|
8672
8671
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8673
8672
|
*/
|
|
8674
|
-
const LATITUDE_PATCH_COUNT$
|
|
8673
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
8675
8674
|
/**
|
|
8676
8675
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
8677
8676
|
*
|
|
8678
8677
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8679
8678
|
*/
|
|
8680
|
-
const LONGITUDE_PATCH_COUNT$
|
|
8679
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
8681
8680
|
/**
|
|
8682
8681
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
8683
8682
|
*
|
|
@@ -8691,8 +8690,8 @@
|
|
|
8691
8690
|
*/
|
|
8692
8691
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
8693
8692
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
8694
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
8695
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
8693
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
8694
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
8696
8695
|
const surfacePatches = [];
|
|
8697
8696
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
8698
8697
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -8751,8 +8750,8 @@
|
|
|
8751
8750
|
transformedCorners[2].z +
|
|
8752
8751
|
transformedCorners[3].z) /
|
|
8753
8752
|
4,
|
|
8754
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
8755
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
8753
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
8754
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
8756
8755
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
8757
8756
|
});
|
|
8758
8757
|
}
|
|
@@ -8820,7 +8819,7 @@
|
|
|
8820
8819
|
*
|
|
8821
8820
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8822
8821
|
*/
|
|
8823
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
8822
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
8824
8823
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
8825
8824
|
if (tonalProgress < 0.16) {
|
|
8826
8825
|
return palette.highlight;
|
|
@@ -8866,7 +8865,7 @@
|
|
|
8866
8865
|
*
|
|
8867
8866
|
* @private helper of `octopus3d3AvatarVisual`
|
|
8868
8867
|
*/
|
|
8869
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
8868
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
8870
8869
|
x: 0.34,
|
|
8871
8870
|
y: -0.62,
|
|
8872
8871
|
z: 1,
|
|
@@ -8876,7 +8875,7 @@
|
|
|
8876
8875
|
*
|
|
8877
8876
|
* @private helper of `octopus3d3AvatarVisual`
|
|
8878
8877
|
*/
|
|
8879
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
8878
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
8880
8879
|
/**
|
|
8881
8880
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
8882
8881
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -9015,9 +9014,9 @@
|
|
|
9015
9014
|
* @private helper of `octopus3d3AvatarVisual`
|
|
9016
9015
|
*/
|
|
9017
9016
|
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
9018
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
9017
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
9019
9018
|
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
9020
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
9019
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
9021
9020
|
return {
|
|
9022
9021
|
centerLongitude: -Math.PI * 0.86 +
|
|
9023
9022
|
progress * Math.PI * 1.72 +
|
|
@@ -9084,13 +9083,13 @@
|
|
|
9084
9083
|
*
|
|
9085
9084
|
* @private helper of `octopus3d3AvatarVisual`
|
|
9086
9085
|
*/
|
|
9087
|
-
const LATITUDE_PATCH_COUNT = 16;
|
|
9086
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
9088
9087
|
/**
|
|
9089
9088
|
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
9090
9089
|
*
|
|
9091
9090
|
* @private helper of `octopus3d3AvatarVisual`
|
|
9092
9091
|
*/
|
|
9093
|
-
const LONGITUDE_PATCH_COUNT = 40;
|
|
9092
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
9094
9093
|
/**
|
|
9095
9094
|
* Resolves visible projected patches for the continuous octopus mesh.
|
|
9096
9095
|
*
|
|
@@ -9104,8 +9103,8 @@
|
|
|
9104
9103
|
*/
|
|
9105
9104
|
function resolveVisibleContinuousOctopusPatches(options) {
|
|
9106
9105
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
9107
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
9108
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
9106
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
9107
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
9109
9108
|
const surfacePatches = [];
|
|
9110
9109
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
9111
9110
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -9173,7 +9172,7 @@
|
|
|
9173
9172
|
transformedCorners[2].z +
|
|
9174
9173
|
transformedCorners[3].z) /
|
|
9175
9174
|
4,
|
|
9176
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
9175
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
9177
9176
|
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
9178
9177
|
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
9179
9178
|
});
|
|
@@ -9207,9 +9206,9 @@
|
|
|
9207
9206
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
9208
9207
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
9209
9208
|
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
9210
|
-
const lowerBlend = smoothStep(0.38, 1, verticalProgress);
|
|
9211
|
-
const tipBlend = smoothStep(0.68, 1, verticalProgress);
|
|
9212
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
9209
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
9210
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
9211
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
9213
9212
|
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
9214
9213
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
9215
9214
|
animationPhase * 0.6 +
|
|
@@ -9275,7 +9274,7 @@
|
|
|
9275
9274
|
let weightedDepthScale = 0;
|
|
9276
9275
|
let weightedPhase = 0;
|
|
9277
9276
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
9278
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
9277
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
9279
9278
|
const width = 0.2 * tentacleProfile.widthScale;
|
|
9280
9279
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
9281
9280
|
totalWeight += weight;
|
|
@@ -9315,7 +9314,7 @@
|
|
|
9315
9314
|
*/
|
|
9316
9315
|
function resolveContinuousLobeWave(options, longitude) {
|
|
9317
9316
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
9318
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
9317
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
9319
9318
|
animationPhase +
|
|
9320
9319
|
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
9321
9320
|
1) /
|
|
@@ -9469,7 +9468,7 @@
|
|
|
9469
9468
|
*
|
|
9470
9469
|
* @private helper of `octopus3d3AvatarVisual`
|
|
9471
9470
|
*/
|
|
9472
|
-
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
9471
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
9473
9472
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
9474
9473
|
}
|
|
9475
9474
|
/**
|
|
@@ -9477,11 +9476,767 @@
|
|
|
9477
9476
|
*
|
|
9478
9477
|
* @private helper of `octopus3d3AvatarVisual`
|
|
9479
9478
|
*/
|
|
9480
|
-
function smoothStep(edgeStart, edgeEnd, value) {
|
|
9479
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
9481
9480
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
9482
9481
|
return progress * progress * (3 - 2 * progress);
|
|
9483
9482
|
}
|
|
9484
9483
|
|
|
9484
|
+
/* eslint-disable no-magic-numbers */
|
|
9485
|
+
/**
|
|
9486
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
9487
|
+
*
|
|
9488
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9489
|
+
*/
|
|
9490
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
9491
|
+
x: 0.32,
|
|
9492
|
+
y: -0.66,
|
|
9493
|
+
z: 1,
|
|
9494
|
+
});
|
|
9495
|
+
/**
|
|
9496
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
9497
|
+
*
|
|
9498
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9499
|
+
*/
|
|
9500
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
9501
|
+
x: -0.7,
|
|
9502
|
+
y: -0.18,
|
|
9503
|
+
z: 0.55,
|
|
9504
|
+
});
|
|
9505
|
+
/**
|
|
9506
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
9507
|
+
*
|
|
9508
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9509
|
+
*/
|
|
9510
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
9511
|
+
/**
|
|
9512
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
9513
|
+
*
|
|
9514
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9515
|
+
*/
|
|
9516
|
+
const SKIN_SPOT_COUNT = 14;
|
|
9517
|
+
/**
|
|
9518
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
9519
|
+
*
|
|
9520
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9521
|
+
*/
|
|
9522
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
9523
|
+
/**
|
|
9524
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
9525
|
+
*
|
|
9526
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9527
|
+
*/
|
|
9528
|
+
function getOctopus3d4StableState(createRandom) {
|
|
9529
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
9530
|
+
if (cached !== undefined) {
|
|
9531
|
+
return cached;
|
|
9532
|
+
}
|
|
9533
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
9534
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
9535
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
9536
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
9537
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
9538
|
+
const state = {
|
|
9539
|
+
morphologyProfile,
|
|
9540
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
9541
|
+
leftEyePhaseOffset,
|
|
9542
|
+
rightEyePhaseOffset,
|
|
9543
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
9544
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
9545
|
+
};
|
|
9546
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
9547
|
+
return state;
|
|
9548
|
+
}
|
|
9549
|
+
/**
|
|
9550
|
+
* Octopus 3D 4 avatar visual.
|
|
9551
|
+
*
|
|
9552
|
+
* @private built-in avatar visual
|
|
9553
|
+
*/
|
|
9554
|
+
const octopus3d4AvatarVisual = {
|
|
9555
|
+
id: 'octopus3d4',
|
|
9556
|
+
title: 'Octopus 3D 4',
|
|
9557
|
+
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.',
|
|
9558
|
+
isAnimated: true,
|
|
9559
|
+
supportsPointerTracking: true,
|
|
9560
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
9561
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
9562
|
+
const sceneCenterX = size * 0.5;
|
|
9563
|
+
const sceneCenterY = size * 0.535;
|
|
9564
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
9565
|
+
const meshCenter = {
|
|
9566
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
9567
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
9568
|
+
z: interaction.intensity * size * 0.02,
|
|
9569
|
+
};
|
|
9570
|
+
const rotationY = -0.08 +
|
|
9571
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
9572
|
+
interaction.bodyOffsetX * 0.24 +
|
|
9573
|
+
interaction.gazeX * 0.98;
|
|
9574
|
+
const rotationX = -0.07 +
|
|
9575
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
9576
|
+
interaction.bodyOffsetY * 0.08 -
|
|
9577
|
+
interaction.gazeY * 0.42;
|
|
9578
|
+
const surfaceOptions = {
|
|
9579
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
9580
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
9581
|
+
radiusZ: size *
|
|
9582
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
9583
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
9584
|
+
morphologyProfile,
|
|
9585
|
+
timeMs,
|
|
9586
|
+
animationPhase,
|
|
9587
|
+
tentacleProfiles,
|
|
9588
|
+
};
|
|
9589
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
9590
|
+
...surfaceOptions,
|
|
9591
|
+
center: meshCenter,
|
|
9592
|
+
rotationX,
|
|
9593
|
+
rotationY,
|
|
9594
|
+
sceneCenterX,
|
|
9595
|
+
sceneCenterY,
|
|
9596
|
+
size,
|
|
9597
|
+
palette,
|
|
9598
|
+
});
|
|
9599
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
9600
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
9601
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
9602
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
9603
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
9604
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
9605
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
9606
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
9607
|
+
drawAvatarFrame(context, size, palette);
|
|
9608
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
9609
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
9610
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
9611
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
9612
|
+
}
|
|
9613
|
+
drawBlobbySkinSpots({
|
|
9614
|
+
context,
|
|
9615
|
+
surfaceOptions,
|
|
9616
|
+
center: meshCenter,
|
|
9617
|
+
rotationX,
|
|
9618
|
+
rotationY,
|
|
9619
|
+
sceneCenterX,
|
|
9620
|
+
sceneCenterY,
|
|
9621
|
+
size,
|
|
9622
|
+
palette,
|
|
9623
|
+
skinSpots,
|
|
9624
|
+
});
|
|
9625
|
+
drawBlobbyContinuousCurrents({
|
|
9626
|
+
context,
|
|
9627
|
+
surfaceOptions,
|
|
9628
|
+
center: meshCenter,
|
|
9629
|
+
rotationX,
|
|
9630
|
+
rotationY,
|
|
9631
|
+
sceneCenterX,
|
|
9632
|
+
sceneCenterY,
|
|
9633
|
+
size,
|
|
9634
|
+
palette,
|
|
9635
|
+
morphologyProfile,
|
|
9636
|
+
timeMs,
|
|
9637
|
+
animationPhase,
|
|
9638
|
+
});
|
|
9639
|
+
drawBlobbyContinuousSuckers({
|
|
9640
|
+
context,
|
|
9641
|
+
surfaceOptions,
|
|
9642
|
+
center: meshCenter,
|
|
9643
|
+
rotationX,
|
|
9644
|
+
rotationY,
|
|
9645
|
+
sceneCenterX,
|
|
9646
|
+
sceneCenterY,
|
|
9647
|
+
size,
|
|
9648
|
+
palette,
|
|
9649
|
+
});
|
|
9650
|
+
drawBlobbyContinuousGloss({
|
|
9651
|
+
context,
|
|
9652
|
+
surfaceOptions,
|
|
9653
|
+
center: meshCenter,
|
|
9654
|
+
rotationX,
|
|
9655
|
+
rotationY,
|
|
9656
|
+
sceneCenterX,
|
|
9657
|
+
sceneCenterY,
|
|
9658
|
+
size,
|
|
9659
|
+
palette,
|
|
9660
|
+
});
|
|
9661
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9662
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9663
|
+
drawProjectedOrganicMouth(context, [
|
|
9664
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
9665
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
9666
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
9667
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
9668
|
+
},
|
|
9669
|
+
};
|
|
9670
|
+
/**
|
|
9671
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
9672
|
+
*
|
|
9673
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9674
|
+
*/
|
|
9675
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
9676
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
9677
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
9678
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
9679
|
+
return {
|
|
9680
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
9681
|
+
progress * Math.PI * 1.8 +
|
|
9682
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
9683
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
9684
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
9685
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
9686
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
9687
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
9688
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9689
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9690
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
9691
|
+
};
|
|
9692
|
+
});
|
|
9693
|
+
}
|
|
9694
|
+
/**
|
|
9695
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
9696
|
+
*
|
|
9697
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9698
|
+
*/
|
|
9699
|
+
function createBlobbySkinSpots(createRandom) {
|
|
9700
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
9701
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
9702
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
9703
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
9704
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
9705
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
9706
|
+
}));
|
|
9707
|
+
}
|
|
9708
|
+
/**
|
|
9709
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
9710
|
+
*
|
|
9711
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9712
|
+
*/
|
|
9713
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
9714
|
+
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));
|
|
9715
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
9716
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
9717
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
9718
|
+
context.fillStyle = glowGradient;
|
|
9719
|
+
context.fillRect(0, 0, size, size);
|
|
9720
|
+
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);
|
|
9721
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
9722
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
9723
|
+
context.fillStyle = lowerGradient;
|
|
9724
|
+
context.fillRect(0, 0, size, size);
|
|
9725
|
+
}
|
|
9726
|
+
/**
|
|
9727
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
9728
|
+
*
|
|
9729
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9730
|
+
*/
|
|
9731
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
9732
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
9733
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
9734
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
9735
|
+
const ry = size * 0.062;
|
|
9736
|
+
context.save();
|
|
9737
|
+
context.translate(cx, cy);
|
|
9738
|
+
context.scale(1, ry / rx);
|
|
9739
|
+
const blurRadius = rx * 1.42;
|
|
9740
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
9741
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
9742
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
9743
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
9744
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
9745
|
+
context.fillStyle = shadowGradient;
|
|
9746
|
+
context.beginPath();
|
|
9747
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
9748
|
+
context.fill();
|
|
9749
|
+
context.restore();
|
|
9750
|
+
}
|
|
9751
|
+
/**
|
|
9752
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
9753
|
+
*
|
|
9754
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9755
|
+
*/
|
|
9756
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
9757
|
+
/**
|
|
9758
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
9759
|
+
*
|
|
9760
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9761
|
+
*/
|
|
9762
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
9763
|
+
/**
|
|
9764
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
9765
|
+
*
|
|
9766
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
9767
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
9768
|
+
* than re-evaluated for every patch corner.
|
|
9769
|
+
*
|
|
9770
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9771
|
+
*/
|
|
9772
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
9773
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
9774
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
9775
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
9776
|
+
const surfacePatches = [];
|
|
9777
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
9778
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
9779
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
9780
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
9781
|
+
}
|
|
9782
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9783
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
9784
|
+
}
|
|
9785
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
9786
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
9787
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
9788
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9789
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9790
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9791
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
9792
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
9793
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
9794
|
+
}
|
|
9795
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9796
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
9797
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
9798
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
9799
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
9800
|
+
}
|
|
9801
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
9802
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
9803
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
9804
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
9805
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
9806
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
9807
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
9808
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
9809
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
9810
|
+
}
|
|
9811
|
+
}
|
|
9812
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
9813
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
9814
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
9815
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
9816
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
9817
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
9818
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
9819
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9820
|
+
const transformedCorners = [
|
|
9821
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
9822
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
9823
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
9824
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
9825
|
+
];
|
|
9826
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
9827
|
+
if (surfaceNormal.z <= 0.006) {
|
|
9828
|
+
continue;
|
|
9829
|
+
}
|
|
9830
|
+
const projectedCorners = [
|
|
9831
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
9832
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
9833
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
9834
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
9835
|
+
];
|
|
9836
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
9837
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
9838
|
+
surfacePatches.push({
|
|
9839
|
+
corners: projectedCorners,
|
|
9840
|
+
averageDepth: (transformedCorners[0].z +
|
|
9841
|
+
transformedCorners[1].z +
|
|
9842
|
+
transformedCorners[2].z +
|
|
9843
|
+
transformedCorners[3].z) /
|
|
9844
|
+
4,
|
|
9845
|
+
lightIntensity,
|
|
9846
|
+
rimLightIntensity,
|
|
9847
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
9848
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
9849
|
+
});
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
return surfacePatches;
|
|
9853
|
+
}
|
|
9854
|
+
/**
|
|
9855
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
9856
|
+
*
|
|
9857
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9858
|
+
*/
|
|
9859
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
9860
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
9861
|
+
}
|
|
9862
|
+
/**
|
|
9863
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
9864
|
+
*
|
|
9865
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
9866
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
9867
|
+
* of `latitudePatchCount * 4` times.
|
|
9868
|
+
*
|
|
9869
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9870
|
+
*/
|
|
9871
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
9872
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
9873
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
9874
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
9875
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
9876
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
9877
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
9878
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
9879
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
9880
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
9881
|
+
animationPhase * 0.6 +
|
|
9882
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
9883
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
9884
|
+
(0.32 + lowerBlend * 0.7);
|
|
9885
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
9886
|
+
lowerBlend *
|
|
9887
|
+
tentacleInfluence.core *
|
|
9888
|
+
tentacleInfluence.swayScale;
|
|
9889
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
9890
|
+
lowerBlend *
|
|
9891
|
+
tentacleInfluence.core *
|
|
9892
|
+
tentacleInfluence.swayScale *
|
|
9893
|
+
0.42;
|
|
9894
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
9895
|
+
tipBlend *
|
|
9896
|
+
tentacleInfluence.core *
|
|
9897
|
+
tentacleInfluence.curlScale *
|
|
9898
|
+
0.36;
|
|
9899
|
+
const horizontalScale = 1.06 +
|
|
9900
|
+
mantleRipple +
|
|
9901
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
9902
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
9903
|
+
upperBlend * 0.08;
|
|
9904
|
+
const depthScale = 1.08 +
|
|
9905
|
+
upperBlend * 0.16 +
|
|
9906
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
9907
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
9908
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
9909
|
+
const tentacleTubeRadius = lowerBlend *
|
|
9910
|
+
tentacleInfluence.core *
|
|
9911
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
9912
|
+
radiusX;
|
|
9913
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
9914
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
9915
|
+
const lowerDrop = lowerBlend *
|
|
9916
|
+
radiusY *
|
|
9917
|
+
(0.2 +
|
|
9918
|
+
tentacleInfluence.core *
|
|
9919
|
+
(0.42 +
|
|
9920
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
9921
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
9922
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
9923
|
+
return {
|
|
9924
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
9925
|
+
combinedTentacleSway +
|
|
9926
|
+
tentacleCurl * radiusX * 0.18,
|
|
9927
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
9928
|
+
upperBlend * radiusY * 0.12 +
|
|
9929
|
+
lowerDrop +
|
|
9930
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
9931
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
9932
|
+
lowerBlend *
|
|
9933
|
+
tipBlend *
|
|
9934
|
+
tentacleInfluence.core *
|
|
9935
|
+
radiusY *
|
|
9936
|
+
0.04,
|
|
9937
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
9938
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
9939
|
+
lowerBlend *
|
|
9940
|
+
tentacleInfluence.core *
|
|
9941
|
+
radiusZ *
|
|
9942
|
+
0.044 +
|
|
9943
|
+
tentacleCurl * radiusZ * 0.14,
|
|
9944
|
+
};
|
|
9945
|
+
}
|
|
9946
|
+
/**
|
|
9947
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
9948
|
+
*
|
|
9949
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9950
|
+
*/
|
|
9951
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
9952
|
+
let totalWeight = 0;
|
|
9953
|
+
let weightedSin = 0;
|
|
9954
|
+
let weightedCos = 0;
|
|
9955
|
+
let weightedWidthScale = 0;
|
|
9956
|
+
let weightedLengthScale = 0;
|
|
9957
|
+
let weightedSwayScale = 0;
|
|
9958
|
+
let weightedDepthScale = 0;
|
|
9959
|
+
let weightedCurlScale = 0;
|
|
9960
|
+
let weightedPrimaryPhase = 0;
|
|
9961
|
+
let weightedSecondaryPhase = 0;
|
|
9962
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
9963
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
9964
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
9965
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
9966
|
+
totalWeight += weight;
|
|
9967
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
9968
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
9969
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
9970
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
9971
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
9972
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
9973
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
9974
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
9975
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
9976
|
+
}
|
|
9977
|
+
if (totalWeight < 0.0001) {
|
|
9978
|
+
return {
|
|
9979
|
+
core: 0,
|
|
9980
|
+
centerLongitude: longitude,
|
|
9981
|
+
widthScale: 1,
|
|
9982
|
+
lengthScale: 1,
|
|
9983
|
+
swayScale: 1,
|
|
9984
|
+
depthScale: 1,
|
|
9985
|
+
curlScale: 1,
|
|
9986
|
+
primaryPhase: 0,
|
|
9987
|
+
secondaryPhase: 0,
|
|
9988
|
+
};
|
|
9989
|
+
}
|
|
9990
|
+
return {
|
|
9991
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
9992
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
9993
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
9994
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
9995
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
9996
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
9997
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
9998
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
9999
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
10000
|
+
};
|
|
10001
|
+
}
|
|
10002
|
+
/**
|
|
10003
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
10004
|
+
*
|
|
10005
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10006
|
+
*/
|
|
10007
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
10008
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
10009
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
10010
|
+
animationPhase +
|
|
10011
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
10012
|
+
1) /
|
|
10013
|
+
2);
|
|
10014
|
+
}
|
|
10015
|
+
/**
|
|
10016
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
10017
|
+
*
|
|
10018
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10019
|
+
*/
|
|
10020
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
10021
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
10022
|
+
if (tonalProgress < 0.12) {
|
|
10023
|
+
return palette.highlight;
|
|
10024
|
+
}
|
|
10025
|
+
if (tonalProgress < 0.3) {
|
|
10026
|
+
return palette.secondary;
|
|
10027
|
+
}
|
|
10028
|
+
if (tonalProgress < 0.72) {
|
|
10029
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
10030
|
+
}
|
|
10031
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
10032
|
+
}
|
|
10033
|
+
/**
|
|
10034
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
10035
|
+
*
|
|
10036
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10037
|
+
*/
|
|
10038
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
10039
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
10040
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
10041
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
10042
|
+
}
|
|
10043
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
10044
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
10045
|
+
}
|
|
10046
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
10047
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
10048
|
+
}
|
|
10049
|
+
context.save();
|
|
10050
|
+
context.beginPath();
|
|
10051
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
10052
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
10053
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
10054
|
+
}
|
|
10055
|
+
context.closePath();
|
|
10056
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
10057
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
10058
|
+
context.lineJoin = 'round';
|
|
10059
|
+
context.stroke();
|
|
10060
|
+
context.restore();
|
|
10061
|
+
}
|
|
10062
|
+
/**
|
|
10063
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
10064
|
+
*
|
|
10065
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10066
|
+
*/
|
|
10067
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
10068
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
10069
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
10070
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
10071
|
+
context.save();
|
|
10072
|
+
context.lineCap = 'round';
|
|
10073
|
+
context.lineJoin = 'round';
|
|
10074
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
10075
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
10076
|
+
const projectedPoints = [];
|
|
10077
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
10078
|
+
const progress = sampleIndex / 8;
|
|
10079
|
+
const latitude = -0.48 + progress * 0.78;
|
|
10080
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
10081
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
10082
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
10083
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
10084
|
+
}
|
|
10085
|
+
}
|
|
10086
|
+
if (projectedPoints.length < 3) {
|
|
10087
|
+
continue;
|
|
10088
|
+
}
|
|
10089
|
+
context.beginPath();
|
|
10090
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
10091
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
10092
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
10093
|
+
}
|
|
10094
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
10095
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
10096
|
+
context.stroke();
|
|
10097
|
+
}
|
|
10098
|
+
context.restore();
|
|
10099
|
+
}
|
|
10100
|
+
/**
|
|
10101
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
10102
|
+
*
|
|
10103
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10104
|
+
*/
|
|
10105
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
10106
|
+
const { surfaceOptions, size } = options;
|
|
10107
|
+
const { timeMs } = surfaceOptions;
|
|
10108
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
10109
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
10110
|
+
continue;
|
|
10111
|
+
}
|
|
10112
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
10113
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
10114
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
10115
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
10116
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
10117
|
+
...options,
|
|
10118
|
+
latitude,
|
|
10119
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
10120
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
10121
|
+
});
|
|
10122
|
+
}
|
|
10123
|
+
}
|
|
10124
|
+
}
|
|
10125
|
+
/**
|
|
10126
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
10127
|
+
*
|
|
10128
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10129
|
+
*/
|
|
10130
|
+
function drawBlobbySkinSpots(options) {
|
|
10131
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
10132
|
+
for (const skinSpot of skinSpots) {
|
|
10133
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
10134
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10135
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
10136
|
+
continue;
|
|
10137
|
+
}
|
|
10138
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10139
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
10140
|
+
context.save();
|
|
10141
|
+
context.beginPath();
|
|
10142
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
10143
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
10144
|
+
context.fill();
|
|
10145
|
+
context.restore();
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
/**
|
|
10149
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
10150
|
+
*
|
|
10151
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10152
|
+
*/
|
|
10153
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
10154
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
10155
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
10156
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
10157
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
10158
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10159
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
10160
|
+
return;
|
|
10161
|
+
}
|
|
10162
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10163
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
10164
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
10165
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
10166
|
+
radiusScale *
|
|
10167
|
+
0.78, size * 0.003, size * 0.02);
|
|
10168
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
10169
|
+
radiusScale *
|
|
10170
|
+
0.54, size * 0.0024, size * 0.015);
|
|
10171
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
10172
|
+
context.save();
|
|
10173
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
10174
|
+
context.rotate(rotation);
|
|
10175
|
+
context.beginPath();
|
|
10176
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
10177
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
10178
|
+
context.fill();
|
|
10179
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
10180
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
10181
|
+
context.stroke();
|
|
10182
|
+
context.restore();
|
|
10183
|
+
}
|
|
10184
|
+
/**
|
|
10185
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
10186
|
+
*
|
|
10187
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10188
|
+
*/
|
|
10189
|
+
function drawBlobbyContinuousGloss(options) {
|
|
10190
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
10191
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
10192
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
10193
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
10194
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
10195
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10196
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
10197
|
+
return;
|
|
10198
|
+
}
|
|
10199
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10200
|
+
const glossRadius = size * 0.058;
|
|
10201
|
+
context.save();
|
|
10202
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
10203
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
10204
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
10205
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
10206
|
+
context.fillStyle = glossGradient;
|
|
10207
|
+
context.beginPath();
|
|
10208
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
10209
|
+
context.fill();
|
|
10210
|
+
context.restore();
|
|
10211
|
+
}
|
|
10212
|
+
/**
|
|
10213
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
10214
|
+
*
|
|
10215
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10216
|
+
*/
|
|
10217
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
10218
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
10219
|
+
}
|
|
10220
|
+
/**
|
|
10221
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
10222
|
+
*
|
|
10223
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10224
|
+
*/
|
|
10225
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
10226
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
10227
|
+
return progress * progress * (3 - 2 * progress);
|
|
10228
|
+
}
|
|
10229
|
+
/**
|
|
10230
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
10231
|
+
*
|
|
10232
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10233
|
+
*/
|
|
10234
|
+
function formatAlphaHex(opacity) {
|
|
10235
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
10236
|
+
.toString(16)
|
|
10237
|
+
.padStart(2, '0');
|
|
10238
|
+
}
|
|
10239
|
+
|
|
9485
10240
|
/* eslint-disable no-magic-numbers */
|
|
9486
10241
|
/**
|
|
9487
10242
|
* Octopus avatar visual.
|
|
@@ -10253,6 +11008,7 @@
|
|
|
10253
11008
|
octopus3dAvatarVisual,
|
|
10254
11009
|
octopus3d2AvatarVisual,
|
|
10255
11010
|
octopus3d3AvatarVisual,
|
|
11011
|
+
octopus3d4AvatarVisual,
|
|
10256
11012
|
asciiOctopusAvatarVisual,
|
|
10257
11013
|
minecraftAvatarVisual,
|
|
10258
11014
|
minecraft2AvatarVisual,
|