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