@promptbook/remote-server 0.112.0-134 → 0.112.0-136
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/_packages/core.index.d.ts +0 -2
- 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/_packages/core.index.d.ts +0 -2
- 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
|
@@ -39,7 +39,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
39
39
|
* @generated
|
|
40
40
|
* @see https://github.com/webgptorg/promptbook
|
|
41
41
|
*/
|
|
42
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
42
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-136';
|
|
43
43
|
/**
|
|
44
44
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
45
45
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -11056,6 +11056,11 @@ function isVoidPseudoAgentReference(rawReference) {
|
|
|
11056
11056
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
11057
11057
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
11058
11058
|
*
|
|
11059
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
11060
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
11061
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
11062
|
+
* outbound request.
|
|
11063
|
+
*
|
|
11059
11064
|
* @public exported from `@promptbook/utils`
|
|
11060
11065
|
*/
|
|
11061
11066
|
function isValidAgentUrl(url) {
|
|
@@ -11069,12 +11074,6 @@ function isValidAgentUrl(url) {
|
|
|
11069
11074
|
// TODO: [🐠]
|
|
11070
11075
|
return false;
|
|
11071
11076
|
}
|
|
11072
|
-
/*
|
|
11073
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
11074
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
11075
|
-
return false;
|
|
11076
|
-
}
|
|
11077
|
-
*/
|
|
11078
11077
|
return true;
|
|
11079
11078
|
}
|
|
11080
11079
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -14723,7 +14722,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
|
|
|
14723
14722
|
*
|
|
14724
14723
|
* @private helper of `minecraft2AvatarVisual`
|
|
14725
14724
|
*/
|
|
14726
|
-
const LIGHT_DIRECTION$
|
|
14725
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
14727
14726
|
x: 0.4,
|
|
14728
14727
|
y: -0.65,
|
|
14729
14728
|
z: 0.92,
|
|
@@ -14947,7 +14946,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
|
|
|
14947
14946
|
corners: projectedCorners,
|
|
14948
14947
|
texture: faceDefinition.texture,
|
|
14949
14948
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
14950
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
14949
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
14951
14950
|
outlineColor: cuboid.outlineColor,
|
|
14952
14951
|
};
|
|
14953
14952
|
});
|
|
@@ -15634,7 +15633,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
|
|
|
15634
15633
|
*
|
|
15635
15634
|
* @private helper of `octopus3AvatarVisual`
|
|
15636
15635
|
*/
|
|
15637
|
-
function formatAlphaHex$
|
|
15636
|
+
function formatAlphaHex$2(opacity) {
|
|
15638
15637
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
15639
15638
|
.toString(16)
|
|
15640
15639
|
.padStart(2, '0');
|
|
@@ -16013,7 +16012,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
|
|
|
16013
16012
|
context.beginPath();
|
|
16014
16013
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
16015
16014
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
16016
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
16015
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
16017
16016
|
context.lineWidth = radiusX * 0.08;
|
|
16018
16017
|
context.lineCap = 'round';
|
|
16019
16018
|
context.stroke();
|
|
@@ -16094,7 +16093,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
|
|
|
16094
16093
|
context.beginPath();
|
|
16095
16094
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16096
16095
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16097
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
16096
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
16098
16097
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
16099
16098
|
context.lineCap = 'round';
|
|
16100
16099
|
context.stroke();
|
|
@@ -16140,7 +16139,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
|
|
|
16140
16139
|
*
|
|
16141
16140
|
* @private helper of the 3D octopus avatar visuals
|
|
16142
16141
|
*/
|
|
16143
|
-
function formatAlphaHex(opacity) {
|
|
16142
|
+
function formatAlphaHex$1(opacity) {
|
|
16144
16143
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
16145
16144
|
.toString(16)
|
|
16146
16145
|
.padStart(2, '0');
|
|
@@ -16152,7 +16151,7 @@ function formatAlphaHex(opacity) {
|
|
|
16152
16151
|
*
|
|
16153
16152
|
* @private helper of `octopus3dAvatarVisual`
|
|
16154
16153
|
*/
|
|
16155
|
-
const LIGHT_DIRECTION$
|
|
16154
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
16156
16155
|
x: 0.48,
|
|
16157
16156
|
y: -0.62,
|
|
16158
16157
|
z: 0.94,
|
|
@@ -16402,7 +16401,7 @@ function resolveVisibleEllipsoidPatches(options) {
|
|
|
16402
16401
|
corners: projectedCorners,
|
|
16403
16402
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
16404
16403
|
transformedCorners.length,
|
|
16405
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
16404
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
16406
16405
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
16407
16406
|
outlineColor,
|
|
16408
16407
|
});
|
|
@@ -16591,7 +16590,7 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
|
|
|
16591
16590
|
*
|
|
16592
16591
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16593
16592
|
*/
|
|
16594
|
-
const LIGHT_DIRECTION$
|
|
16593
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
16595
16594
|
x: 0.38,
|
|
16596
16595
|
y: -0.6,
|
|
16597
16596
|
z: 0.98,
|
|
@@ -16750,13 +16749,13 @@ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, mo
|
|
|
16750
16749
|
*
|
|
16751
16750
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16752
16751
|
*/
|
|
16753
|
-
const LATITUDE_PATCH_COUNT$
|
|
16752
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
16754
16753
|
/**
|
|
16755
16754
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
16756
16755
|
*
|
|
16757
16756
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16758
16757
|
*/
|
|
16759
|
-
const LONGITUDE_PATCH_COUNT$
|
|
16758
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
16760
16759
|
/**
|
|
16761
16760
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
16762
16761
|
*
|
|
@@ -16770,8 +16769,8 @@ const LONGITUDE_PATCH_COUNT$1 = 24;
|
|
|
16770
16769
|
*/
|
|
16771
16770
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
16772
16771
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
16773
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
16774
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
16772
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
16773
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
16775
16774
|
const surfacePatches = [];
|
|
16776
16775
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16777
16776
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -16830,8 +16829,8 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
16830
16829
|
transformedCorners[2].z +
|
|
16831
16830
|
transformedCorners[3].z) /
|
|
16832
16831
|
4,
|
|
16833
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
16834
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
16832
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
16833
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
16835
16834
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
16836
16835
|
});
|
|
16837
16836
|
}
|
|
@@ -16899,7 +16898,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
|
|
|
16899
16898
|
*
|
|
16900
16899
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16901
16900
|
*/
|
|
16902
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16901
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16903
16902
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
16904
16903
|
if (tonalProgress < 0.16) {
|
|
16905
16904
|
return palette.highlight;
|
|
@@ -16945,7 +16944,7 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
|
|
|
16945
16944
|
*
|
|
16946
16945
|
* @private helper of `octopus3d3AvatarVisual`
|
|
16947
16946
|
*/
|
|
16948
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
16947
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
16949
16948
|
x: 0.34,
|
|
16950
16949
|
y: -0.62,
|
|
16951
16950
|
z: 1,
|
|
@@ -16955,7 +16954,7 @@ const LIGHT_DIRECTION = normalizeVector3({
|
|
|
16955
16954
|
*
|
|
16956
16955
|
* @private helper of `octopus3d3AvatarVisual`
|
|
16957
16956
|
*/
|
|
16958
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
16957
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
16959
16958
|
/**
|
|
16960
16959
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
16961
16960
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -17094,9 +17093,9 @@ const octopus3d3AvatarVisual = {
|
|
|
17094
17093
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17095
17094
|
*/
|
|
17096
17095
|
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17097
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
17096
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
17098
17097
|
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
17099
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
17098
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
17100
17099
|
return {
|
|
17101
17100
|
centerLongitude: -Math.PI * 0.86 +
|
|
17102
17101
|
progress * Math.PI * 1.72 +
|
|
@@ -17163,13 +17162,13 @@ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs
|
|
|
17163
17162
|
*
|
|
17164
17163
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17165
17164
|
*/
|
|
17166
|
-
const LATITUDE_PATCH_COUNT = 16;
|
|
17165
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
17167
17166
|
/**
|
|
17168
17167
|
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
17169
17168
|
*
|
|
17170
17169
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17171
17170
|
*/
|
|
17172
|
-
const LONGITUDE_PATCH_COUNT = 40;
|
|
17171
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
17173
17172
|
/**
|
|
17174
17173
|
* Resolves visible projected patches for the continuous octopus mesh.
|
|
17175
17174
|
*
|
|
@@ -17183,8 +17182,8 @@ const LONGITUDE_PATCH_COUNT = 40;
|
|
|
17183
17182
|
*/
|
|
17184
17183
|
function resolveVisibleContinuousOctopusPatches(options) {
|
|
17185
17184
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17186
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
17187
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
17185
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
17186
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
17188
17187
|
const surfacePatches = [];
|
|
17189
17188
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17190
17189
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -17252,7 +17251,7 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
17252
17251
|
transformedCorners[2].z +
|
|
17253
17252
|
transformedCorners[3].z) /
|
|
17254
17253
|
4,
|
|
17255
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
17254
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
17256
17255
|
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17257
17256
|
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
17258
17257
|
});
|
|
@@ -17286,9 +17285,9 @@ function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude
|
|
|
17286
17285
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17287
17286
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17288
17287
|
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
17289
|
-
const lowerBlend = smoothStep(0.38, 1, verticalProgress);
|
|
17290
|
-
const tipBlend = smoothStep(0.68, 1, verticalProgress);
|
|
17291
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
17288
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
17289
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
17290
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
17292
17291
|
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
17293
17292
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17294
17293
|
animationPhase * 0.6 +
|
|
@@ -17354,7 +17353,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
17354
17353
|
let weightedDepthScale = 0;
|
|
17355
17354
|
let weightedPhase = 0;
|
|
17356
17355
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
17357
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
17356
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
17358
17357
|
const width = 0.2 * tentacleProfile.widthScale;
|
|
17359
17358
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
17360
17359
|
totalWeight += weight;
|
|
@@ -17394,7 +17393,7 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
17394
17393
|
*/
|
|
17395
17394
|
function resolveContinuousLobeWave(options, longitude) {
|
|
17396
17395
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
17397
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
17396
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
17398
17397
|
animationPhase +
|
|
17399
17398
|
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
17400
17399
|
1) /
|
|
@@ -17548,7 +17547,7 @@ function drawProjectedSurfaceSpot(options) {
|
|
|
17548
17547
|
*
|
|
17549
17548
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17550
17549
|
*/
|
|
17551
|
-
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
17550
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
17552
17551
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
17553
17552
|
}
|
|
17554
17553
|
/**
|
|
@@ -17556,11 +17555,767 @@ function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
|
17556
17555
|
*
|
|
17557
17556
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17558
17557
|
*/
|
|
17559
|
-
function smoothStep(edgeStart, edgeEnd, value) {
|
|
17558
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
17560
17559
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
17561
17560
|
return progress * progress * (3 - 2 * progress);
|
|
17562
17561
|
}
|
|
17563
17562
|
|
|
17563
|
+
/* eslint-disable no-magic-numbers */
|
|
17564
|
+
/**
|
|
17565
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
17566
|
+
*
|
|
17567
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17568
|
+
*/
|
|
17569
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
17570
|
+
x: 0.32,
|
|
17571
|
+
y: -0.66,
|
|
17572
|
+
z: 1,
|
|
17573
|
+
});
|
|
17574
|
+
/**
|
|
17575
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
17576
|
+
*
|
|
17577
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17578
|
+
*/
|
|
17579
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
17580
|
+
x: -0.7,
|
|
17581
|
+
y: -0.18,
|
|
17582
|
+
z: 0.55,
|
|
17583
|
+
});
|
|
17584
|
+
/**
|
|
17585
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
17586
|
+
*
|
|
17587
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17588
|
+
*/
|
|
17589
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
17590
|
+
/**
|
|
17591
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
17592
|
+
*
|
|
17593
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17594
|
+
*/
|
|
17595
|
+
const SKIN_SPOT_COUNT = 14;
|
|
17596
|
+
/**
|
|
17597
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
17598
|
+
*
|
|
17599
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17600
|
+
*/
|
|
17601
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
17602
|
+
/**
|
|
17603
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
17604
|
+
*
|
|
17605
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17606
|
+
*/
|
|
17607
|
+
function getOctopus3d4StableState(createRandom) {
|
|
17608
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
17609
|
+
if (cached !== undefined) {
|
|
17610
|
+
return cached;
|
|
17611
|
+
}
|
|
17612
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
17613
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
17614
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
17615
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
17616
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
17617
|
+
const state = {
|
|
17618
|
+
morphologyProfile,
|
|
17619
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
17620
|
+
leftEyePhaseOffset,
|
|
17621
|
+
rightEyePhaseOffset,
|
|
17622
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
17623
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
17624
|
+
};
|
|
17625
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
17626
|
+
return state;
|
|
17627
|
+
}
|
|
17628
|
+
/**
|
|
17629
|
+
* Octopus 3D 4 avatar visual.
|
|
17630
|
+
*
|
|
17631
|
+
* @private built-in avatar visual
|
|
17632
|
+
*/
|
|
17633
|
+
const octopus3d4AvatarVisual = {
|
|
17634
|
+
id: 'octopus3d4',
|
|
17635
|
+
title: 'Octopus 3D 4',
|
|
17636
|
+
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.',
|
|
17637
|
+
isAnimated: true,
|
|
17638
|
+
supportsPointerTracking: true,
|
|
17639
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
17640
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
17641
|
+
const sceneCenterX = size * 0.5;
|
|
17642
|
+
const sceneCenterY = size * 0.535;
|
|
17643
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
17644
|
+
const meshCenter = {
|
|
17645
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
17646
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
17647
|
+
z: interaction.intensity * size * 0.02,
|
|
17648
|
+
};
|
|
17649
|
+
const rotationY = -0.08 +
|
|
17650
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
17651
|
+
interaction.bodyOffsetX * 0.24 +
|
|
17652
|
+
interaction.gazeX * 0.98;
|
|
17653
|
+
const rotationX = -0.07 +
|
|
17654
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
17655
|
+
interaction.bodyOffsetY * 0.08 -
|
|
17656
|
+
interaction.gazeY * 0.42;
|
|
17657
|
+
const surfaceOptions = {
|
|
17658
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
17659
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
17660
|
+
radiusZ: size *
|
|
17661
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
17662
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
17663
|
+
morphologyProfile,
|
|
17664
|
+
timeMs,
|
|
17665
|
+
animationPhase,
|
|
17666
|
+
tentacleProfiles,
|
|
17667
|
+
};
|
|
17668
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
17669
|
+
...surfaceOptions,
|
|
17670
|
+
center: meshCenter,
|
|
17671
|
+
rotationX,
|
|
17672
|
+
rotationY,
|
|
17673
|
+
sceneCenterX,
|
|
17674
|
+
sceneCenterY,
|
|
17675
|
+
size,
|
|
17676
|
+
palette,
|
|
17677
|
+
});
|
|
17678
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
17679
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
17680
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
17681
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
17682
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
17683
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
17684
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
17685
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
17686
|
+
drawAvatarFrame(context, size, palette);
|
|
17687
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
17688
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
17689
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
17690
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
17691
|
+
}
|
|
17692
|
+
drawBlobbySkinSpots({
|
|
17693
|
+
context,
|
|
17694
|
+
surfaceOptions,
|
|
17695
|
+
center: meshCenter,
|
|
17696
|
+
rotationX,
|
|
17697
|
+
rotationY,
|
|
17698
|
+
sceneCenterX,
|
|
17699
|
+
sceneCenterY,
|
|
17700
|
+
size,
|
|
17701
|
+
palette,
|
|
17702
|
+
skinSpots,
|
|
17703
|
+
});
|
|
17704
|
+
drawBlobbyContinuousCurrents({
|
|
17705
|
+
context,
|
|
17706
|
+
surfaceOptions,
|
|
17707
|
+
center: meshCenter,
|
|
17708
|
+
rotationX,
|
|
17709
|
+
rotationY,
|
|
17710
|
+
sceneCenterX,
|
|
17711
|
+
sceneCenterY,
|
|
17712
|
+
size,
|
|
17713
|
+
palette,
|
|
17714
|
+
morphologyProfile,
|
|
17715
|
+
timeMs,
|
|
17716
|
+
animationPhase,
|
|
17717
|
+
});
|
|
17718
|
+
drawBlobbyContinuousSuckers({
|
|
17719
|
+
context,
|
|
17720
|
+
surfaceOptions,
|
|
17721
|
+
center: meshCenter,
|
|
17722
|
+
rotationX,
|
|
17723
|
+
rotationY,
|
|
17724
|
+
sceneCenterX,
|
|
17725
|
+
sceneCenterY,
|
|
17726
|
+
size,
|
|
17727
|
+
palette,
|
|
17728
|
+
});
|
|
17729
|
+
drawBlobbyContinuousGloss({
|
|
17730
|
+
context,
|
|
17731
|
+
surfaceOptions,
|
|
17732
|
+
center: meshCenter,
|
|
17733
|
+
rotationX,
|
|
17734
|
+
rotationY,
|
|
17735
|
+
sceneCenterX,
|
|
17736
|
+
sceneCenterY,
|
|
17737
|
+
size,
|
|
17738
|
+
palette,
|
|
17739
|
+
});
|
|
17740
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17741
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17742
|
+
drawProjectedOrganicMouth(context, [
|
|
17743
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
17744
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
17745
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
17746
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
17747
|
+
},
|
|
17748
|
+
};
|
|
17749
|
+
/**
|
|
17750
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
17751
|
+
*
|
|
17752
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17753
|
+
*/
|
|
17754
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17755
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
17756
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
17757
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
17758
|
+
return {
|
|
17759
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
17760
|
+
progress * Math.PI * 1.8 +
|
|
17761
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
17762
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
17763
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
17764
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
17765
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
17766
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
17767
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17768
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17769
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
17770
|
+
};
|
|
17771
|
+
});
|
|
17772
|
+
}
|
|
17773
|
+
/**
|
|
17774
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
17775
|
+
*
|
|
17776
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17777
|
+
*/
|
|
17778
|
+
function createBlobbySkinSpots(createRandom) {
|
|
17779
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
17780
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
17781
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
17782
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
17783
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
17784
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
17785
|
+
}));
|
|
17786
|
+
}
|
|
17787
|
+
/**
|
|
17788
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
17789
|
+
*
|
|
17790
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17791
|
+
*/
|
|
17792
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
17793
|
+
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));
|
|
17794
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
17795
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
17796
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
17797
|
+
context.fillStyle = glowGradient;
|
|
17798
|
+
context.fillRect(0, 0, size, size);
|
|
17799
|
+
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);
|
|
17800
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
17801
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
17802
|
+
context.fillStyle = lowerGradient;
|
|
17803
|
+
context.fillRect(0, 0, size, size);
|
|
17804
|
+
}
|
|
17805
|
+
/**
|
|
17806
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
17807
|
+
*
|
|
17808
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17809
|
+
*/
|
|
17810
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
17811
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
17812
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
17813
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
17814
|
+
const ry = size * 0.062;
|
|
17815
|
+
context.save();
|
|
17816
|
+
context.translate(cx, cy);
|
|
17817
|
+
context.scale(1, ry / rx);
|
|
17818
|
+
const blurRadius = rx * 1.42;
|
|
17819
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
17820
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
17821
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
17822
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
17823
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
17824
|
+
context.fillStyle = shadowGradient;
|
|
17825
|
+
context.beginPath();
|
|
17826
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
17827
|
+
context.fill();
|
|
17828
|
+
context.restore();
|
|
17829
|
+
}
|
|
17830
|
+
/**
|
|
17831
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17832
|
+
*
|
|
17833
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17834
|
+
*/
|
|
17835
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
17836
|
+
/**
|
|
17837
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17838
|
+
*
|
|
17839
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17840
|
+
*/
|
|
17841
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
17842
|
+
/**
|
|
17843
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
17844
|
+
*
|
|
17845
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
17846
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
17847
|
+
* than re-evaluated for every patch corner.
|
|
17848
|
+
*
|
|
17849
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17850
|
+
*/
|
|
17851
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
17852
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17853
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
17854
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
17855
|
+
const surfacePatches = [];
|
|
17856
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17857
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
17858
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
17859
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
17860
|
+
}
|
|
17861
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17862
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
17863
|
+
}
|
|
17864
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
17865
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
17866
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
17867
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17868
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17869
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17870
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
17871
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
17872
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
17873
|
+
}
|
|
17874
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17875
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
17876
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
17877
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
17878
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
17879
|
+
}
|
|
17880
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
17881
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
17882
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
17883
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
17884
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
17885
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
17886
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
17887
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
17888
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
17889
|
+
}
|
|
17890
|
+
}
|
|
17891
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
17892
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
17893
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
17894
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
17895
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
17896
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
17897
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
17898
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17899
|
+
const transformedCorners = [
|
|
17900
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
17901
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
17902
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
17903
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
17904
|
+
];
|
|
17905
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
17906
|
+
if (surfaceNormal.z <= 0.006) {
|
|
17907
|
+
continue;
|
|
17908
|
+
}
|
|
17909
|
+
const projectedCorners = [
|
|
17910
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
17911
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
17912
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
17913
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
17914
|
+
];
|
|
17915
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
17916
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
17917
|
+
surfacePatches.push({
|
|
17918
|
+
corners: projectedCorners,
|
|
17919
|
+
averageDepth: (transformedCorners[0].z +
|
|
17920
|
+
transformedCorners[1].z +
|
|
17921
|
+
transformedCorners[2].z +
|
|
17922
|
+
transformedCorners[3].z) /
|
|
17923
|
+
4,
|
|
17924
|
+
lightIntensity,
|
|
17925
|
+
rimLightIntensity,
|
|
17926
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17927
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
17928
|
+
});
|
|
17929
|
+
}
|
|
17930
|
+
}
|
|
17931
|
+
return surfacePatches;
|
|
17932
|
+
}
|
|
17933
|
+
/**
|
|
17934
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
17935
|
+
*
|
|
17936
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17937
|
+
*/
|
|
17938
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
17939
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
17940
|
+
}
|
|
17941
|
+
/**
|
|
17942
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
17943
|
+
*
|
|
17944
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
17945
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
17946
|
+
* of `latitudePatchCount * 4` times.
|
|
17947
|
+
*
|
|
17948
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17949
|
+
*/
|
|
17950
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
17951
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
17952
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17953
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17954
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
17955
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
17956
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
17957
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
17958
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
17959
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17960
|
+
animationPhase * 0.6 +
|
|
17961
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
17962
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
17963
|
+
(0.32 + lowerBlend * 0.7);
|
|
17964
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
17965
|
+
lowerBlend *
|
|
17966
|
+
tentacleInfluence.core *
|
|
17967
|
+
tentacleInfluence.swayScale;
|
|
17968
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
17969
|
+
lowerBlend *
|
|
17970
|
+
tentacleInfluence.core *
|
|
17971
|
+
tentacleInfluence.swayScale *
|
|
17972
|
+
0.42;
|
|
17973
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
17974
|
+
tipBlend *
|
|
17975
|
+
tentacleInfluence.core *
|
|
17976
|
+
tentacleInfluence.curlScale *
|
|
17977
|
+
0.36;
|
|
17978
|
+
const horizontalScale = 1.06 +
|
|
17979
|
+
mantleRipple +
|
|
17980
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
17981
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
17982
|
+
upperBlend * 0.08;
|
|
17983
|
+
const depthScale = 1.08 +
|
|
17984
|
+
upperBlend * 0.16 +
|
|
17985
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
17986
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
17987
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
17988
|
+
const tentacleTubeRadius = lowerBlend *
|
|
17989
|
+
tentacleInfluence.core *
|
|
17990
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
17991
|
+
radiusX;
|
|
17992
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
17993
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
17994
|
+
const lowerDrop = lowerBlend *
|
|
17995
|
+
radiusY *
|
|
17996
|
+
(0.2 +
|
|
17997
|
+
tentacleInfluence.core *
|
|
17998
|
+
(0.42 +
|
|
17999
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
18000
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
18001
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
18002
|
+
return {
|
|
18003
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
18004
|
+
combinedTentacleSway +
|
|
18005
|
+
tentacleCurl * radiusX * 0.18,
|
|
18006
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
18007
|
+
upperBlend * radiusY * 0.12 +
|
|
18008
|
+
lowerDrop +
|
|
18009
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
18010
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
18011
|
+
lowerBlend *
|
|
18012
|
+
tipBlend *
|
|
18013
|
+
tentacleInfluence.core *
|
|
18014
|
+
radiusY *
|
|
18015
|
+
0.04,
|
|
18016
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
18017
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
18018
|
+
lowerBlend *
|
|
18019
|
+
tentacleInfluence.core *
|
|
18020
|
+
radiusZ *
|
|
18021
|
+
0.044 +
|
|
18022
|
+
tentacleCurl * radiusZ * 0.14,
|
|
18023
|
+
};
|
|
18024
|
+
}
|
|
18025
|
+
/**
|
|
18026
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
18027
|
+
*
|
|
18028
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18029
|
+
*/
|
|
18030
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
18031
|
+
let totalWeight = 0;
|
|
18032
|
+
let weightedSin = 0;
|
|
18033
|
+
let weightedCos = 0;
|
|
18034
|
+
let weightedWidthScale = 0;
|
|
18035
|
+
let weightedLengthScale = 0;
|
|
18036
|
+
let weightedSwayScale = 0;
|
|
18037
|
+
let weightedDepthScale = 0;
|
|
18038
|
+
let weightedCurlScale = 0;
|
|
18039
|
+
let weightedPrimaryPhase = 0;
|
|
18040
|
+
let weightedSecondaryPhase = 0;
|
|
18041
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
18042
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
18043
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
18044
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
18045
|
+
totalWeight += weight;
|
|
18046
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
18047
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
18048
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
18049
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
18050
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
18051
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
18052
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
18053
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
18054
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
18055
|
+
}
|
|
18056
|
+
if (totalWeight < 0.0001) {
|
|
18057
|
+
return {
|
|
18058
|
+
core: 0,
|
|
18059
|
+
centerLongitude: longitude,
|
|
18060
|
+
widthScale: 1,
|
|
18061
|
+
lengthScale: 1,
|
|
18062
|
+
swayScale: 1,
|
|
18063
|
+
depthScale: 1,
|
|
18064
|
+
curlScale: 1,
|
|
18065
|
+
primaryPhase: 0,
|
|
18066
|
+
secondaryPhase: 0,
|
|
18067
|
+
};
|
|
18068
|
+
}
|
|
18069
|
+
return {
|
|
18070
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
18071
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
18072
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
18073
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
18074
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
18075
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
18076
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
18077
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
18078
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
18079
|
+
};
|
|
18080
|
+
}
|
|
18081
|
+
/**
|
|
18082
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
18083
|
+
*
|
|
18084
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18085
|
+
*/
|
|
18086
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
18087
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
18088
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
18089
|
+
animationPhase +
|
|
18090
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
18091
|
+
1) /
|
|
18092
|
+
2);
|
|
18093
|
+
}
|
|
18094
|
+
/**
|
|
18095
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
18096
|
+
*
|
|
18097
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18098
|
+
*/
|
|
18099
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
18100
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
18101
|
+
if (tonalProgress < 0.12) {
|
|
18102
|
+
return palette.highlight;
|
|
18103
|
+
}
|
|
18104
|
+
if (tonalProgress < 0.3) {
|
|
18105
|
+
return palette.secondary;
|
|
18106
|
+
}
|
|
18107
|
+
if (tonalProgress < 0.72) {
|
|
18108
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
18109
|
+
}
|
|
18110
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
18111
|
+
}
|
|
18112
|
+
/**
|
|
18113
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
18114
|
+
*
|
|
18115
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18116
|
+
*/
|
|
18117
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
18118
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
18119
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
18120
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
18121
|
+
}
|
|
18122
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
18123
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
18124
|
+
}
|
|
18125
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
18126
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
18127
|
+
}
|
|
18128
|
+
context.save();
|
|
18129
|
+
context.beginPath();
|
|
18130
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
18131
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
18132
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
18133
|
+
}
|
|
18134
|
+
context.closePath();
|
|
18135
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
18136
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
18137
|
+
context.lineJoin = 'round';
|
|
18138
|
+
context.stroke();
|
|
18139
|
+
context.restore();
|
|
18140
|
+
}
|
|
18141
|
+
/**
|
|
18142
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
18143
|
+
*
|
|
18144
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18145
|
+
*/
|
|
18146
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
18147
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
18148
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
18149
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
18150
|
+
context.save();
|
|
18151
|
+
context.lineCap = 'round';
|
|
18152
|
+
context.lineJoin = 'round';
|
|
18153
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
18154
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
18155
|
+
const projectedPoints = [];
|
|
18156
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
18157
|
+
const progress = sampleIndex / 8;
|
|
18158
|
+
const latitude = -0.48 + progress * 0.78;
|
|
18159
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
18160
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
18161
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
18162
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
18163
|
+
}
|
|
18164
|
+
}
|
|
18165
|
+
if (projectedPoints.length < 3) {
|
|
18166
|
+
continue;
|
|
18167
|
+
}
|
|
18168
|
+
context.beginPath();
|
|
18169
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
18170
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
18171
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
18172
|
+
}
|
|
18173
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
18174
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
18175
|
+
context.stroke();
|
|
18176
|
+
}
|
|
18177
|
+
context.restore();
|
|
18178
|
+
}
|
|
18179
|
+
/**
|
|
18180
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
18181
|
+
*
|
|
18182
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18183
|
+
*/
|
|
18184
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
18185
|
+
const { surfaceOptions, size } = options;
|
|
18186
|
+
const { timeMs } = surfaceOptions;
|
|
18187
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
18188
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
18189
|
+
continue;
|
|
18190
|
+
}
|
|
18191
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
18192
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
18193
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
18194
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
18195
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
18196
|
+
...options,
|
|
18197
|
+
latitude,
|
|
18198
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
18199
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
18200
|
+
});
|
|
18201
|
+
}
|
|
18202
|
+
}
|
|
18203
|
+
}
|
|
18204
|
+
/**
|
|
18205
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
18206
|
+
*
|
|
18207
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18208
|
+
*/
|
|
18209
|
+
function drawBlobbySkinSpots(options) {
|
|
18210
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
18211
|
+
for (const skinSpot of skinSpots) {
|
|
18212
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
18213
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18214
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
18215
|
+
continue;
|
|
18216
|
+
}
|
|
18217
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18218
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
18219
|
+
context.save();
|
|
18220
|
+
context.beginPath();
|
|
18221
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
18222
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
18223
|
+
context.fill();
|
|
18224
|
+
context.restore();
|
|
18225
|
+
}
|
|
18226
|
+
}
|
|
18227
|
+
/**
|
|
18228
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
18229
|
+
*
|
|
18230
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18231
|
+
*/
|
|
18232
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
18233
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
18234
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
18235
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
18236
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
18237
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18238
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
18239
|
+
return;
|
|
18240
|
+
}
|
|
18241
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18242
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18243
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18244
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
18245
|
+
radiusScale *
|
|
18246
|
+
0.78, size * 0.003, size * 0.02);
|
|
18247
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
18248
|
+
radiusScale *
|
|
18249
|
+
0.54, size * 0.0024, size * 0.015);
|
|
18250
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
18251
|
+
context.save();
|
|
18252
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
18253
|
+
context.rotate(rotation);
|
|
18254
|
+
context.beginPath();
|
|
18255
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
18256
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
18257
|
+
context.fill();
|
|
18258
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
18259
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
18260
|
+
context.stroke();
|
|
18261
|
+
context.restore();
|
|
18262
|
+
}
|
|
18263
|
+
/**
|
|
18264
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
18265
|
+
*
|
|
18266
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18267
|
+
*/
|
|
18268
|
+
function drawBlobbyContinuousGloss(options) {
|
|
18269
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
18270
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
18271
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
18272
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
18273
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
18274
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18275
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
18276
|
+
return;
|
|
18277
|
+
}
|
|
18278
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18279
|
+
const glossRadius = size * 0.058;
|
|
18280
|
+
context.save();
|
|
18281
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
18282
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
18283
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
18284
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
18285
|
+
context.fillStyle = glossGradient;
|
|
18286
|
+
context.beginPath();
|
|
18287
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
18288
|
+
context.fill();
|
|
18289
|
+
context.restore();
|
|
18290
|
+
}
|
|
18291
|
+
/**
|
|
18292
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
18293
|
+
*
|
|
18294
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18295
|
+
*/
|
|
18296
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
18297
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
18298
|
+
}
|
|
18299
|
+
/**
|
|
18300
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
18301
|
+
*
|
|
18302
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18303
|
+
*/
|
|
18304
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
18305
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
18306
|
+
return progress * progress * (3 - 2 * progress);
|
|
18307
|
+
}
|
|
18308
|
+
/**
|
|
18309
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
18310
|
+
*
|
|
18311
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18312
|
+
*/
|
|
18313
|
+
function formatAlphaHex(opacity) {
|
|
18314
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
18315
|
+
.toString(16)
|
|
18316
|
+
.padStart(2, '0');
|
|
18317
|
+
}
|
|
18318
|
+
|
|
17564
18319
|
/* eslint-disable no-magic-numbers */
|
|
17565
18320
|
/**
|
|
17566
18321
|
* Octopus avatar visual.
|
|
@@ -18332,6 +19087,7 @@ const AVATAR_VISUALS = [
|
|
|
18332
19087
|
octopus3dAvatarVisual,
|
|
18333
19088
|
octopus3d2AvatarVisual,
|
|
18334
19089
|
octopus3d3AvatarVisual,
|
|
19090
|
+
octopus3d4AvatarVisual,
|
|
18335
19091
|
asciiOctopusAvatarVisual,
|
|
18336
19092
|
minecraftAvatarVisual,
|
|
18337
19093
|
minecraft2AvatarVisual,
|