@promptbook/wizard 0.112.0-134 → 0.112.0-135
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/index.es.js +795 -39
- package/esm/index.es.js.map +1 -1
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/package.json +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/esm/index.es.js
CHANGED
|
@@ -37,7 +37,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
37
37
|
* @generated
|
|
38
38
|
* @see https://github.com/webgptorg/promptbook
|
|
39
39
|
*/
|
|
40
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
40
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-135';
|
|
41
41
|
/**
|
|
42
42
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
43
43
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -19171,6 +19171,11 @@ class FormatCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
19171
19171
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
19172
19172
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
19173
19173
|
*
|
|
19174
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
19175
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
19176
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
19177
|
+
* outbound request.
|
|
19178
|
+
*
|
|
19174
19179
|
* @public exported from `@promptbook/utils`
|
|
19175
19180
|
*/
|
|
19176
19181
|
function isValidAgentUrl(url) {
|
|
@@ -19184,12 +19189,6 @@ function isValidAgentUrl(url) {
|
|
|
19184
19189
|
// TODO: [🐠]
|
|
19185
19190
|
return false;
|
|
19186
19191
|
}
|
|
19187
|
-
/*
|
|
19188
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
19189
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
19190
|
-
return false;
|
|
19191
|
-
}
|
|
19192
|
-
*/
|
|
19193
19192
|
return true;
|
|
19194
19193
|
}
|
|
19195
19194
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -22579,7 +22578,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
|
|
|
22579
22578
|
*
|
|
22580
22579
|
* @private helper of `minecraft2AvatarVisual`
|
|
22581
22580
|
*/
|
|
22582
|
-
const LIGHT_DIRECTION$
|
|
22581
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
22583
22582
|
x: 0.4,
|
|
22584
22583
|
y: -0.65,
|
|
22585
22584
|
z: 0.92,
|
|
@@ -22803,7 +22802,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
|
|
|
22803
22802
|
corners: projectedCorners,
|
|
22804
22803
|
texture: faceDefinition.texture,
|
|
22805
22804
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
22806
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
22805
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
22807
22806
|
outlineColor: cuboid.outlineColor,
|
|
22808
22807
|
};
|
|
22809
22808
|
});
|
|
@@ -23490,7 +23489,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
|
|
|
23490
23489
|
*
|
|
23491
23490
|
* @private helper of `octopus3AvatarVisual`
|
|
23492
23491
|
*/
|
|
23493
|
-
function formatAlphaHex$
|
|
23492
|
+
function formatAlphaHex$2(opacity) {
|
|
23494
23493
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
23495
23494
|
.toString(16)
|
|
23496
23495
|
.padStart(2, '0');
|
|
@@ -23869,7 +23868,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
|
|
|
23869
23868
|
context.beginPath();
|
|
23870
23869
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
23871
23870
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
23872
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
23871
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
23873
23872
|
context.lineWidth = radiusX * 0.08;
|
|
23874
23873
|
context.lineCap = 'round';
|
|
23875
23874
|
context.stroke();
|
|
@@ -23950,7 +23949,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
|
|
|
23950
23949
|
context.beginPath();
|
|
23951
23950
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
23952
23951
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
23953
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
23952
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
23954
23953
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
23955
23954
|
context.lineCap = 'round';
|
|
23956
23955
|
context.stroke();
|
|
@@ -23996,7 +23995,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
|
|
|
23996
23995
|
*
|
|
23997
23996
|
* @private helper of the 3D octopus avatar visuals
|
|
23998
23997
|
*/
|
|
23999
|
-
function formatAlphaHex(opacity) {
|
|
23998
|
+
function formatAlphaHex$1(opacity) {
|
|
24000
23999
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
24001
24000
|
.toString(16)
|
|
24002
24001
|
.padStart(2, '0');
|
|
@@ -24008,7 +24007,7 @@ function formatAlphaHex(opacity) {
|
|
|
24008
24007
|
*
|
|
24009
24008
|
* @private helper of `octopus3dAvatarVisual`
|
|
24010
24009
|
*/
|
|
24011
|
-
const LIGHT_DIRECTION$
|
|
24010
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
24012
24011
|
x: 0.48,
|
|
24013
24012
|
y: -0.62,
|
|
24014
24013
|
z: 0.94,
|
|
@@ -24258,7 +24257,7 @@ function resolveVisibleEllipsoidPatches(options) {
|
|
|
24258
24257
|
corners: projectedCorners,
|
|
24259
24258
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
24260
24259
|
transformedCorners.length,
|
|
24261
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
24260
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
24262
24261
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
24263
24262
|
outlineColor,
|
|
24264
24263
|
});
|
|
@@ -24447,7 +24446,7 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
|
|
|
24447
24446
|
*
|
|
24448
24447
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24449
24448
|
*/
|
|
24450
|
-
const LIGHT_DIRECTION$
|
|
24449
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
24451
24450
|
x: 0.38,
|
|
24452
24451
|
y: -0.6,
|
|
24453
24452
|
z: 0.98,
|
|
@@ -24606,13 +24605,13 @@ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, mo
|
|
|
24606
24605
|
*
|
|
24607
24606
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24608
24607
|
*/
|
|
24609
|
-
const LATITUDE_PATCH_COUNT$
|
|
24608
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
24610
24609
|
/**
|
|
24611
24610
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
24612
24611
|
*
|
|
24613
24612
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24614
24613
|
*/
|
|
24615
|
-
const LONGITUDE_PATCH_COUNT$
|
|
24614
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
24616
24615
|
/**
|
|
24617
24616
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
24618
24617
|
*
|
|
@@ -24626,8 +24625,8 @@ const LONGITUDE_PATCH_COUNT$1 = 24;
|
|
|
24626
24625
|
*/
|
|
24627
24626
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
24628
24627
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
24629
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
24630
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
24628
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
24629
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
24631
24630
|
const surfacePatches = [];
|
|
24632
24631
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
24633
24632
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -24686,8 +24685,8 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
24686
24685
|
transformedCorners[2].z +
|
|
24687
24686
|
transformedCorners[3].z) /
|
|
24688
24687
|
4,
|
|
24689
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
24690
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
24688
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
24689
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
24691
24690
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
24692
24691
|
});
|
|
24693
24692
|
}
|
|
@@ -24755,7 +24754,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
|
|
|
24755
24754
|
*
|
|
24756
24755
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24757
24756
|
*/
|
|
24758
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
24757
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
24759
24758
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
24760
24759
|
if (tonalProgress < 0.16) {
|
|
24761
24760
|
return palette.highlight;
|
|
@@ -24801,7 +24800,7 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
|
|
|
24801
24800
|
*
|
|
24802
24801
|
* @private helper of `octopus3d3AvatarVisual`
|
|
24803
24802
|
*/
|
|
24804
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
24803
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
24805
24804
|
x: 0.34,
|
|
24806
24805
|
y: -0.62,
|
|
24807
24806
|
z: 1,
|
|
@@ -24811,7 +24810,7 @@ const LIGHT_DIRECTION = normalizeVector3({
|
|
|
24811
24810
|
*
|
|
24812
24811
|
* @private helper of `octopus3d3AvatarVisual`
|
|
24813
24812
|
*/
|
|
24814
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
24813
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
24815
24814
|
/**
|
|
24816
24815
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
24817
24816
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -24950,9 +24949,9 @@ const octopus3d3AvatarVisual = {
|
|
|
24950
24949
|
* @private helper of `octopus3d3AvatarVisual`
|
|
24951
24950
|
*/
|
|
24952
24951
|
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
24953
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
24952
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
24954
24953
|
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
24955
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
24954
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
24956
24955
|
return {
|
|
24957
24956
|
centerLongitude: -Math.PI * 0.86 +
|
|
24958
24957
|
progress * Math.PI * 1.72 +
|
|
@@ -25019,13 +25018,13 @@ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs
|
|
|
25019
25018
|
*
|
|
25020
25019
|
* @private helper of `octopus3d3AvatarVisual`
|
|
25021
25020
|
*/
|
|
25022
|
-
const LATITUDE_PATCH_COUNT = 16;
|
|
25021
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
25023
25022
|
/**
|
|
25024
25023
|
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
25025
25024
|
*
|
|
25026
25025
|
* @private helper of `octopus3d3AvatarVisual`
|
|
25027
25026
|
*/
|
|
25028
|
-
const LONGITUDE_PATCH_COUNT = 40;
|
|
25027
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
25029
25028
|
/**
|
|
25030
25029
|
* Resolves visible projected patches for the continuous octopus mesh.
|
|
25031
25030
|
*
|
|
@@ -25039,8 +25038,8 @@ const LONGITUDE_PATCH_COUNT = 40;
|
|
|
25039
25038
|
*/
|
|
25040
25039
|
function resolveVisibleContinuousOctopusPatches(options) {
|
|
25041
25040
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
25042
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
25043
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
25041
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
25042
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
25044
25043
|
const surfacePatches = [];
|
|
25045
25044
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
25046
25045
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -25108,7 +25107,7 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
25108
25107
|
transformedCorners[2].z +
|
|
25109
25108
|
transformedCorners[3].z) /
|
|
25110
25109
|
4,
|
|
25111
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
25110
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
25112
25111
|
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
25113
25112
|
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
25114
25113
|
});
|
|
@@ -25142,9 +25141,9 @@ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude
|
|
|
25142
25141
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
25143
25142
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
25144
25143
|
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
25145
|
-
const lowerBlend = smoothStep(0.38, 1, verticalProgress);
|
|
25146
|
-
const tipBlend = smoothStep(0.68, 1, verticalProgress);
|
|
25147
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
25144
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
25145
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
25146
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
25148
25147
|
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
25149
25148
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
25150
25149
|
animationPhase * 0.6 +
|
|
@@ -25210,7 +25209,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
25210
25209
|
let weightedDepthScale = 0;
|
|
25211
25210
|
let weightedPhase = 0;
|
|
25212
25211
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
25213
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
25212
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
25214
25213
|
const width = 0.2 * tentacleProfile.widthScale;
|
|
25215
25214
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
25216
25215
|
totalWeight += weight;
|
|
@@ -25250,7 +25249,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
25250
25249
|
*/
|
|
25251
25250
|
function resolveContinuousLobeWave(options, longitude) {
|
|
25252
25251
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
25253
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
25252
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
25254
25253
|
animationPhase +
|
|
25255
25254
|
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
25256
25255
|
1) /
|
|
@@ -25404,7 +25403,7 @@ function drawProjectedSurfaceSpot(options) {
|
|
|
25404
25403
|
*
|
|
25405
25404
|
* @private helper of `octopus3d3AvatarVisual`
|
|
25406
25405
|
*/
|
|
25407
|
-
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
25406
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
25408
25407
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
25409
25408
|
}
|
|
25410
25409
|
/**
|
|
@@ -25412,11 +25411,767 @@ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
|
25412
25411
|
*
|
|
25413
25412
|
* @private helper of `octopus3d3AvatarVisual`
|
|
25414
25413
|
*/
|
|
25415
|
-
function smoothStep(edgeStart, edgeEnd, value) {
|
|
25414
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
25416
25415
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
25417
25416
|
return progress * progress * (3 - 2 * progress);
|
|
25418
25417
|
}
|
|
25419
25418
|
|
|
25419
|
+
/* eslint-disable no-magic-numbers */
|
|
25420
|
+
/**
|
|
25421
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
25422
|
+
*
|
|
25423
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25424
|
+
*/
|
|
25425
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
25426
|
+
x: 0.32,
|
|
25427
|
+
y: -0.66,
|
|
25428
|
+
z: 1,
|
|
25429
|
+
});
|
|
25430
|
+
/**
|
|
25431
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
25432
|
+
*
|
|
25433
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25434
|
+
*/
|
|
25435
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
25436
|
+
x: -0.7,
|
|
25437
|
+
y: -0.18,
|
|
25438
|
+
z: 0.55,
|
|
25439
|
+
});
|
|
25440
|
+
/**
|
|
25441
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
25442
|
+
*
|
|
25443
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25444
|
+
*/
|
|
25445
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
25446
|
+
/**
|
|
25447
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
25448
|
+
*
|
|
25449
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25450
|
+
*/
|
|
25451
|
+
const SKIN_SPOT_COUNT = 14;
|
|
25452
|
+
/**
|
|
25453
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
25454
|
+
*
|
|
25455
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25456
|
+
*/
|
|
25457
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
25458
|
+
/**
|
|
25459
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
25460
|
+
*
|
|
25461
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25462
|
+
*/
|
|
25463
|
+
function getOctopus3d4StableState(createRandom) {
|
|
25464
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
25465
|
+
if (cached !== undefined) {
|
|
25466
|
+
return cached;
|
|
25467
|
+
}
|
|
25468
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
25469
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
25470
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
25471
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
25472
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
25473
|
+
const state = {
|
|
25474
|
+
morphologyProfile,
|
|
25475
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
25476
|
+
leftEyePhaseOffset,
|
|
25477
|
+
rightEyePhaseOffset,
|
|
25478
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
25479
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
25480
|
+
};
|
|
25481
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
25482
|
+
return state;
|
|
25483
|
+
}
|
|
25484
|
+
/**
|
|
25485
|
+
* Octopus 3D 4 avatar visual.
|
|
25486
|
+
*
|
|
25487
|
+
* @private built-in avatar visual
|
|
25488
|
+
*/
|
|
25489
|
+
const octopus3d4AvatarVisual = {
|
|
25490
|
+
id: 'octopus3d4',
|
|
25491
|
+
title: 'Octopus 3D 4',
|
|
25492
|
+
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.',
|
|
25493
|
+
isAnimated: true,
|
|
25494
|
+
supportsPointerTracking: true,
|
|
25495
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
25496
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
25497
|
+
const sceneCenterX = size * 0.5;
|
|
25498
|
+
const sceneCenterY = size * 0.535;
|
|
25499
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
25500
|
+
const meshCenter = {
|
|
25501
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
25502
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
25503
|
+
z: interaction.intensity * size * 0.02,
|
|
25504
|
+
};
|
|
25505
|
+
const rotationY = -0.08 +
|
|
25506
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
25507
|
+
interaction.bodyOffsetX * 0.24 +
|
|
25508
|
+
interaction.gazeX * 0.98;
|
|
25509
|
+
const rotationX = -0.07 +
|
|
25510
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
25511
|
+
interaction.bodyOffsetY * 0.08 -
|
|
25512
|
+
interaction.gazeY * 0.42;
|
|
25513
|
+
const surfaceOptions = {
|
|
25514
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
25515
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
25516
|
+
radiusZ: size *
|
|
25517
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
25518
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
25519
|
+
morphologyProfile,
|
|
25520
|
+
timeMs,
|
|
25521
|
+
animationPhase,
|
|
25522
|
+
tentacleProfiles,
|
|
25523
|
+
};
|
|
25524
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
25525
|
+
...surfaceOptions,
|
|
25526
|
+
center: meshCenter,
|
|
25527
|
+
rotationX,
|
|
25528
|
+
rotationY,
|
|
25529
|
+
sceneCenterX,
|
|
25530
|
+
sceneCenterY,
|
|
25531
|
+
size,
|
|
25532
|
+
palette,
|
|
25533
|
+
});
|
|
25534
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
25535
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
25536
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
25537
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
25538
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
25539
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
25540
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
25541
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
25542
|
+
drawAvatarFrame(context, size, palette);
|
|
25543
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
25544
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
25545
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
25546
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
25547
|
+
}
|
|
25548
|
+
drawBlobbySkinSpots({
|
|
25549
|
+
context,
|
|
25550
|
+
surfaceOptions,
|
|
25551
|
+
center: meshCenter,
|
|
25552
|
+
rotationX,
|
|
25553
|
+
rotationY,
|
|
25554
|
+
sceneCenterX,
|
|
25555
|
+
sceneCenterY,
|
|
25556
|
+
size,
|
|
25557
|
+
palette,
|
|
25558
|
+
skinSpots,
|
|
25559
|
+
});
|
|
25560
|
+
drawBlobbyContinuousCurrents({
|
|
25561
|
+
context,
|
|
25562
|
+
surfaceOptions,
|
|
25563
|
+
center: meshCenter,
|
|
25564
|
+
rotationX,
|
|
25565
|
+
rotationY,
|
|
25566
|
+
sceneCenterX,
|
|
25567
|
+
sceneCenterY,
|
|
25568
|
+
size,
|
|
25569
|
+
palette,
|
|
25570
|
+
morphologyProfile,
|
|
25571
|
+
timeMs,
|
|
25572
|
+
animationPhase,
|
|
25573
|
+
});
|
|
25574
|
+
drawBlobbyContinuousSuckers({
|
|
25575
|
+
context,
|
|
25576
|
+
surfaceOptions,
|
|
25577
|
+
center: meshCenter,
|
|
25578
|
+
rotationX,
|
|
25579
|
+
rotationY,
|
|
25580
|
+
sceneCenterX,
|
|
25581
|
+
sceneCenterY,
|
|
25582
|
+
size,
|
|
25583
|
+
palette,
|
|
25584
|
+
});
|
|
25585
|
+
drawBlobbyContinuousGloss({
|
|
25586
|
+
context,
|
|
25587
|
+
surfaceOptions,
|
|
25588
|
+
center: meshCenter,
|
|
25589
|
+
rotationX,
|
|
25590
|
+
rotationY,
|
|
25591
|
+
sceneCenterX,
|
|
25592
|
+
sceneCenterY,
|
|
25593
|
+
size,
|
|
25594
|
+
palette,
|
|
25595
|
+
});
|
|
25596
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
25597
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
25598
|
+
drawProjectedOrganicMouth(context, [
|
|
25599
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
25600
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
25601
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
25602
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
25603
|
+
},
|
|
25604
|
+
};
|
|
25605
|
+
/**
|
|
25606
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
25607
|
+
*
|
|
25608
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25609
|
+
*/
|
|
25610
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
25611
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
25612
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
25613
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
25614
|
+
return {
|
|
25615
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
25616
|
+
progress * Math.PI * 1.8 +
|
|
25617
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
25618
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
25619
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
25620
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
25621
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
25622
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
25623
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
25624
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
25625
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
25626
|
+
};
|
|
25627
|
+
});
|
|
25628
|
+
}
|
|
25629
|
+
/**
|
|
25630
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
25631
|
+
*
|
|
25632
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25633
|
+
*/
|
|
25634
|
+
function createBlobbySkinSpots(createRandom) {
|
|
25635
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
25636
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
25637
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
25638
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
25639
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
25640
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
25641
|
+
}));
|
|
25642
|
+
}
|
|
25643
|
+
/**
|
|
25644
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
25645
|
+
*
|
|
25646
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25647
|
+
*/
|
|
25648
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
25649
|
+
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));
|
|
25650
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
25651
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
25652
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
25653
|
+
context.fillStyle = glowGradient;
|
|
25654
|
+
context.fillRect(0, 0, size, size);
|
|
25655
|
+
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);
|
|
25656
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
25657
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
25658
|
+
context.fillStyle = lowerGradient;
|
|
25659
|
+
context.fillRect(0, 0, size, size);
|
|
25660
|
+
}
|
|
25661
|
+
/**
|
|
25662
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
25663
|
+
*
|
|
25664
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25665
|
+
*/
|
|
25666
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
25667
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
25668
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
25669
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
25670
|
+
const ry = size * 0.062;
|
|
25671
|
+
context.save();
|
|
25672
|
+
context.translate(cx, cy);
|
|
25673
|
+
context.scale(1, ry / rx);
|
|
25674
|
+
const blurRadius = rx * 1.42;
|
|
25675
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
25676
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
25677
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
25678
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
25679
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
25680
|
+
context.fillStyle = shadowGradient;
|
|
25681
|
+
context.beginPath();
|
|
25682
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
25683
|
+
context.fill();
|
|
25684
|
+
context.restore();
|
|
25685
|
+
}
|
|
25686
|
+
/**
|
|
25687
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
25688
|
+
*
|
|
25689
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25690
|
+
*/
|
|
25691
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
25692
|
+
/**
|
|
25693
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
25694
|
+
*
|
|
25695
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25696
|
+
*/
|
|
25697
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
25698
|
+
/**
|
|
25699
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
25700
|
+
*
|
|
25701
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
25702
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
25703
|
+
* than re-evaluated for every patch corner.
|
|
25704
|
+
*
|
|
25705
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25706
|
+
*/
|
|
25707
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
25708
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
25709
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
25710
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
25711
|
+
const surfacePatches = [];
|
|
25712
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
25713
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
25714
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
25715
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
25716
|
+
}
|
|
25717
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
25718
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
25719
|
+
}
|
|
25720
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
25721
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
25722
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
25723
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
25724
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
25725
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
25726
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
25727
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
25728
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
25729
|
+
}
|
|
25730
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
25731
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
25732
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
25733
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
25734
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
25735
|
+
}
|
|
25736
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
25737
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
25738
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
25739
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
25740
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
25741
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
25742
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
25743
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
25744
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
25745
|
+
}
|
|
25746
|
+
}
|
|
25747
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
25748
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
25749
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
25750
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
25751
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
25752
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
25753
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
25754
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
25755
|
+
const transformedCorners = [
|
|
25756
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
25757
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
25758
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
25759
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
25760
|
+
];
|
|
25761
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
25762
|
+
if (surfaceNormal.z <= 0.006) {
|
|
25763
|
+
continue;
|
|
25764
|
+
}
|
|
25765
|
+
const projectedCorners = [
|
|
25766
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
25767
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
25768
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
25769
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
25770
|
+
];
|
|
25771
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
25772
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
25773
|
+
surfacePatches.push({
|
|
25774
|
+
corners: projectedCorners,
|
|
25775
|
+
averageDepth: (transformedCorners[0].z +
|
|
25776
|
+
transformedCorners[1].z +
|
|
25777
|
+
transformedCorners[2].z +
|
|
25778
|
+
transformedCorners[3].z) /
|
|
25779
|
+
4,
|
|
25780
|
+
lightIntensity,
|
|
25781
|
+
rimLightIntensity,
|
|
25782
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
25783
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
25784
|
+
});
|
|
25785
|
+
}
|
|
25786
|
+
}
|
|
25787
|
+
return surfacePatches;
|
|
25788
|
+
}
|
|
25789
|
+
/**
|
|
25790
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
25791
|
+
*
|
|
25792
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25793
|
+
*/
|
|
25794
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
25795
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
25796
|
+
}
|
|
25797
|
+
/**
|
|
25798
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
25799
|
+
*
|
|
25800
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
25801
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
25802
|
+
* of `latitudePatchCount * 4` times.
|
|
25803
|
+
*
|
|
25804
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25805
|
+
*/
|
|
25806
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
25807
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
25808
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
25809
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
25810
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
25811
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
25812
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
25813
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
25814
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
25815
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
25816
|
+
animationPhase * 0.6 +
|
|
25817
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
25818
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
25819
|
+
(0.32 + lowerBlend * 0.7);
|
|
25820
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
25821
|
+
lowerBlend *
|
|
25822
|
+
tentacleInfluence.core *
|
|
25823
|
+
tentacleInfluence.swayScale;
|
|
25824
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
25825
|
+
lowerBlend *
|
|
25826
|
+
tentacleInfluence.core *
|
|
25827
|
+
tentacleInfluence.swayScale *
|
|
25828
|
+
0.42;
|
|
25829
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
25830
|
+
tipBlend *
|
|
25831
|
+
tentacleInfluence.core *
|
|
25832
|
+
tentacleInfluence.curlScale *
|
|
25833
|
+
0.36;
|
|
25834
|
+
const horizontalScale = 1.06 +
|
|
25835
|
+
mantleRipple +
|
|
25836
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
25837
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
25838
|
+
upperBlend * 0.08;
|
|
25839
|
+
const depthScale = 1.08 +
|
|
25840
|
+
upperBlend * 0.16 +
|
|
25841
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
25842
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
25843
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
25844
|
+
const tentacleTubeRadius = lowerBlend *
|
|
25845
|
+
tentacleInfluence.core *
|
|
25846
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
25847
|
+
radiusX;
|
|
25848
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
25849
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
25850
|
+
const lowerDrop = lowerBlend *
|
|
25851
|
+
radiusY *
|
|
25852
|
+
(0.2 +
|
|
25853
|
+
tentacleInfluence.core *
|
|
25854
|
+
(0.42 +
|
|
25855
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
25856
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
25857
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
25858
|
+
return {
|
|
25859
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
25860
|
+
combinedTentacleSway +
|
|
25861
|
+
tentacleCurl * radiusX * 0.18,
|
|
25862
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
25863
|
+
upperBlend * radiusY * 0.12 +
|
|
25864
|
+
lowerDrop +
|
|
25865
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
25866
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
25867
|
+
lowerBlend *
|
|
25868
|
+
tipBlend *
|
|
25869
|
+
tentacleInfluence.core *
|
|
25870
|
+
radiusY *
|
|
25871
|
+
0.04,
|
|
25872
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
25873
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
25874
|
+
lowerBlend *
|
|
25875
|
+
tentacleInfluence.core *
|
|
25876
|
+
radiusZ *
|
|
25877
|
+
0.044 +
|
|
25878
|
+
tentacleCurl * radiusZ * 0.14,
|
|
25879
|
+
};
|
|
25880
|
+
}
|
|
25881
|
+
/**
|
|
25882
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
25883
|
+
*
|
|
25884
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25885
|
+
*/
|
|
25886
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
25887
|
+
let totalWeight = 0;
|
|
25888
|
+
let weightedSin = 0;
|
|
25889
|
+
let weightedCos = 0;
|
|
25890
|
+
let weightedWidthScale = 0;
|
|
25891
|
+
let weightedLengthScale = 0;
|
|
25892
|
+
let weightedSwayScale = 0;
|
|
25893
|
+
let weightedDepthScale = 0;
|
|
25894
|
+
let weightedCurlScale = 0;
|
|
25895
|
+
let weightedPrimaryPhase = 0;
|
|
25896
|
+
let weightedSecondaryPhase = 0;
|
|
25897
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
25898
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
25899
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
25900
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
25901
|
+
totalWeight += weight;
|
|
25902
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
25903
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
25904
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
25905
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
25906
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
25907
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
25908
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
25909
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
25910
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
25911
|
+
}
|
|
25912
|
+
if (totalWeight < 0.0001) {
|
|
25913
|
+
return {
|
|
25914
|
+
core: 0,
|
|
25915
|
+
centerLongitude: longitude,
|
|
25916
|
+
widthScale: 1,
|
|
25917
|
+
lengthScale: 1,
|
|
25918
|
+
swayScale: 1,
|
|
25919
|
+
depthScale: 1,
|
|
25920
|
+
curlScale: 1,
|
|
25921
|
+
primaryPhase: 0,
|
|
25922
|
+
secondaryPhase: 0,
|
|
25923
|
+
};
|
|
25924
|
+
}
|
|
25925
|
+
return {
|
|
25926
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
25927
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
25928
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
25929
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
25930
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
25931
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
25932
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
25933
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
25934
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
25935
|
+
};
|
|
25936
|
+
}
|
|
25937
|
+
/**
|
|
25938
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
25939
|
+
*
|
|
25940
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25941
|
+
*/
|
|
25942
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
25943
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
25944
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
25945
|
+
animationPhase +
|
|
25946
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
25947
|
+
1) /
|
|
25948
|
+
2);
|
|
25949
|
+
}
|
|
25950
|
+
/**
|
|
25951
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
25952
|
+
*
|
|
25953
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25954
|
+
*/
|
|
25955
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
25956
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
25957
|
+
if (tonalProgress < 0.12) {
|
|
25958
|
+
return palette.highlight;
|
|
25959
|
+
}
|
|
25960
|
+
if (tonalProgress < 0.3) {
|
|
25961
|
+
return palette.secondary;
|
|
25962
|
+
}
|
|
25963
|
+
if (tonalProgress < 0.72) {
|
|
25964
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
25965
|
+
}
|
|
25966
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
25967
|
+
}
|
|
25968
|
+
/**
|
|
25969
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
25970
|
+
*
|
|
25971
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25972
|
+
*/
|
|
25973
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
25974
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
25975
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
25976
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
25977
|
+
}
|
|
25978
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
25979
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
25980
|
+
}
|
|
25981
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
25982
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
25983
|
+
}
|
|
25984
|
+
context.save();
|
|
25985
|
+
context.beginPath();
|
|
25986
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
25987
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
25988
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
25989
|
+
}
|
|
25990
|
+
context.closePath();
|
|
25991
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
25992
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
25993
|
+
context.lineJoin = 'round';
|
|
25994
|
+
context.stroke();
|
|
25995
|
+
context.restore();
|
|
25996
|
+
}
|
|
25997
|
+
/**
|
|
25998
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
25999
|
+
*
|
|
26000
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26001
|
+
*/
|
|
26002
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
26003
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
26004
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
26005
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
26006
|
+
context.save();
|
|
26007
|
+
context.lineCap = 'round';
|
|
26008
|
+
context.lineJoin = 'round';
|
|
26009
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
26010
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
26011
|
+
const projectedPoints = [];
|
|
26012
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
26013
|
+
const progress = sampleIndex / 8;
|
|
26014
|
+
const latitude = -0.48 + progress * 0.78;
|
|
26015
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
26016
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
26017
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
26018
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
26019
|
+
}
|
|
26020
|
+
}
|
|
26021
|
+
if (projectedPoints.length < 3) {
|
|
26022
|
+
continue;
|
|
26023
|
+
}
|
|
26024
|
+
context.beginPath();
|
|
26025
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
26026
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
26027
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
26028
|
+
}
|
|
26029
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
26030
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
26031
|
+
context.stroke();
|
|
26032
|
+
}
|
|
26033
|
+
context.restore();
|
|
26034
|
+
}
|
|
26035
|
+
/**
|
|
26036
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
26037
|
+
*
|
|
26038
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26039
|
+
*/
|
|
26040
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
26041
|
+
const { surfaceOptions, size } = options;
|
|
26042
|
+
const { timeMs } = surfaceOptions;
|
|
26043
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
26044
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
26045
|
+
continue;
|
|
26046
|
+
}
|
|
26047
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
26048
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
26049
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
26050
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
26051
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
26052
|
+
...options,
|
|
26053
|
+
latitude,
|
|
26054
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
26055
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
26056
|
+
});
|
|
26057
|
+
}
|
|
26058
|
+
}
|
|
26059
|
+
}
|
|
26060
|
+
/**
|
|
26061
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
26062
|
+
*
|
|
26063
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26064
|
+
*/
|
|
26065
|
+
function drawBlobbySkinSpots(options) {
|
|
26066
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
26067
|
+
for (const skinSpot of skinSpots) {
|
|
26068
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
26069
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
26070
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
26071
|
+
continue;
|
|
26072
|
+
}
|
|
26073
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
26074
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
26075
|
+
context.save();
|
|
26076
|
+
context.beginPath();
|
|
26077
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
26078
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
26079
|
+
context.fill();
|
|
26080
|
+
context.restore();
|
|
26081
|
+
}
|
|
26082
|
+
}
|
|
26083
|
+
/**
|
|
26084
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
26085
|
+
*
|
|
26086
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26087
|
+
*/
|
|
26088
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
26089
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
26090
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
26091
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
26092
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
26093
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
26094
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
26095
|
+
return;
|
|
26096
|
+
}
|
|
26097
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
26098
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
26099
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
26100
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
26101
|
+
radiusScale *
|
|
26102
|
+
0.78, size * 0.003, size * 0.02);
|
|
26103
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
26104
|
+
radiusScale *
|
|
26105
|
+
0.54, size * 0.0024, size * 0.015);
|
|
26106
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
26107
|
+
context.save();
|
|
26108
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
26109
|
+
context.rotate(rotation);
|
|
26110
|
+
context.beginPath();
|
|
26111
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
26112
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
26113
|
+
context.fill();
|
|
26114
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
26115
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
26116
|
+
context.stroke();
|
|
26117
|
+
context.restore();
|
|
26118
|
+
}
|
|
26119
|
+
/**
|
|
26120
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
26121
|
+
*
|
|
26122
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26123
|
+
*/
|
|
26124
|
+
function drawBlobbyContinuousGloss(options) {
|
|
26125
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
26126
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
26127
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
26128
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
26129
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
26130
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
26131
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
26132
|
+
return;
|
|
26133
|
+
}
|
|
26134
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
26135
|
+
const glossRadius = size * 0.058;
|
|
26136
|
+
context.save();
|
|
26137
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
26138
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
26139
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
26140
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
26141
|
+
context.fillStyle = glossGradient;
|
|
26142
|
+
context.beginPath();
|
|
26143
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
26144
|
+
context.fill();
|
|
26145
|
+
context.restore();
|
|
26146
|
+
}
|
|
26147
|
+
/**
|
|
26148
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
26149
|
+
*
|
|
26150
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26151
|
+
*/
|
|
26152
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
26153
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
26154
|
+
}
|
|
26155
|
+
/**
|
|
26156
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
26157
|
+
*
|
|
26158
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26159
|
+
*/
|
|
26160
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
26161
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
26162
|
+
return progress * progress * (3 - 2 * progress);
|
|
26163
|
+
}
|
|
26164
|
+
/**
|
|
26165
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
26166
|
+
*
|
|
26167
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26168
|
+
*/
|
|
26169
|
+
function formatAlphaHex(opacity) {
|
|
26170
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
26171
|
+
.toString(16)
|
|
26172
|
+
.padStart(2, '0');
|
|
26173
|
+
}
|
|
26174
|
+
|
|
25420
26175
|
/* eslint-disable no-magic-numbers */
|
|
25421
26176
|
/**
|
|
25422
26177
|
* Octopus avatar visual.
|
|
@@ -26188,6 +26943,7 @@ const AVATAR_VISUALS = [
|
|
|
26188
26943
|
octopus3dAvatarVisual,
|
|
26189
26944
|
octopus3d2AvatarVisual,
|
|
26190
26945
|
octopus3d3AvatarVisual,
|
|
26946
|
+
octopus3d4AvatarVisual,
|
|
26191
26947
|
asciiOctopusAvatarVisual,
|
|
26192
26948
|
minecraftAvatarVisual,
|
|
26193
26949
|
minecraft2AvatarVisual,
|