@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/umd/index.umd.js
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
* @generated
|
|
54
54
|
* @see https://github.com/webgptorg/promptbook
|
|
55
55
|
*/
|
|
56
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
56
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-136';
|
|
57
57
|
/**
|
|
58
58
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
59
59
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -11070,6 +11070,11 @@
|
|
|
11070
11070
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
11071
11071
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
11072
11072
|
*
|
|
11073
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
11074
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
11075
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
11076
|
+
* outbound request.
|
|
11077
|
+
*
|
|
11073
11078
|
* @public exported from `@promptbook/utils`
|
|
11074
11079
|
*/
|
|
11075
11080
|
function isValidAgentUrl(url) {
|
|
@@ -11083,12 +11088,6 @@
|
|
|
11083
11088
|
// TODO: [🐠]
|
|
11084
11089
|
return false;
|
|
11085
11090
|
}
|
|
11086
|
-
/*
|
|
11087
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
11088
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
11089
|
-
return false;
|
|
11090
|
-
}
|
|
11091
|
-
*/
|
|
11092
11091
|
return true;
|
|
11093
11092
|
}
|
|
11094
11093
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -14737,7 +14736,7 @@
|
|
|
14737
14736
|
*
|
|
14738
14737
|
* @private helper of `minecraft2AvatarVisual`
|
|
14739
14738
|
*/
|
|
14740
|
-
const LIGHT_DIRECTION$
|
|
14739
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
14741
14740
|
x: 0.4,
|
|
14742
14741
|
y: -0.65,
|
|
14743
14742
|
z: 0.92,
|
|
@@ -14961,7 +14960,7 @@
|
|
|
14961
14960
|
corners: projectedCorners,
|
|
14962
14961
|
texture: faceDefinition.texture,
|
|
14963
14962
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
14964
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
14963
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
14965
14964
|
outlineColor: cuboid.outlineColor,
|
|
14966
14965
|
};
|
|
14967
14966
|
});
|
|
@@ -15648,7 +15647,7 @@
|
|
|
15648
15647
|
*
|
|
15649
15648
|
* @private helper of `octopus3AvatarVisual`
|
|
15650
15649
|
*/
|
|
15651
|
-
function formatAlphaHex$
|
|
15650
|
+
function formatAlphaHex$2(opacity) {
|
|
15652
15651
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
15653
15652
|
.toString(16)
|
|
15654
15653
|
.padStart(2, '0');
|
|
@@ -16027,7 +16026,7 @@
|
|
|
16027
16026
|
context.beginPath();
|
|
16028
16027
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
16029
16028
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
16030
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
16029
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
16031
16030
|
context.lineWidth = radiusX * 0.08;
|
|
16032
16031
|
context.lineCap = 'round';
|
|
16033
16032
|
context.stroke();
|
|
@@ -16108,7 +16107,7 @@
|
|
|
16108
16107
|
context.beginPath();
|
|
16109
16108
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16110
16109
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16111
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
16110
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
16112
16111
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
16113
16112
|
context.lineCap = 'round';
|
|
16114
16113
|
context.stroke();
|
|
@@ -16154,7 +16153,7 @@
|
|
|
16154
16153
|
*
|
|
16155
16154
|
* @private helper of the 3D octopus avatar visuals
|
|
16156
16155
|
*/
|
|
16157
|
-
function formatAlphaHex(opacity) {
|
|
16156
|
+
function formatAlphaHex$1(opacity) {
|
|
16158
16157
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
16159
16158
|
.toString(16)
|
|
16160
16159
|
.padStart(2, '0');
|
|
@@ -16166,7 +16165,7 @@
|
|
|
16166
16165
|
*
|
|
16167
16166
|
* @private helper of `octopus3dAvatarVisual`
|
|
16168
16167
|
*/
|
|
16169
|
-
const LIGHT_DIRECTION$
|
|
16168
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
16170
16169
|
x: 0.48,
|
|
16171
16170
|
y: -0.62,
|
|
16172
16171
|
z: 0.94,
|
|
@@ -16416,7 +16415,7 @@
|
|
|
16416
16415
|
corners: projectedCorners,
|
|
16417
16416
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
16418
16417
|
transformedCorners.length,
|
|
16419
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
16418
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
16420
16419
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
16421
16420
|
outlineColor,
|
|
16422
16421
|
});
|
|
@@ -16605,7 +16604,7 @@
|
|
|
16605
16604
|
*
|
|
16606
16605
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16607
16606
|
*/
|
|
16608
|
-
const LIGHT_DIRECTION$
|
|
16607
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
16609
16608
|
x: 0.38,
|
|
16610
16609
|
y: -0.6,
|
|
16611
16610
|
z: 0.98,
|
|
@@ -16764,13 +16763,13 @@
|
|
|
16764
16763
|
*
|
|
16765
16764
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16766
16765
|
*/
|
|
16767
|
-
const LATITUDE_PATCH_COUNT$
|
|
16766
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
16768
16767
|
/**
|
|
16769
16768
|
* Number of longitude segments used by the single blobby octopus mesh.
|
|
16770
16769
|
*
|
|
16771
16770
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16772
16771
|
*/
|
|
16773
|
-
const LONGITUDE_PATCH_COUNT$
|
|
16772
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
16774
16773
|
/**
|
|
16775
16774
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
16776
16775
|
*
|
|
@@ -16784,8 +16783,8 @@
|
|
|
16784
16783
|
*/
|
|
16785
16784
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
16786
16785
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
16787
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
16788
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
16786
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
16787
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
16789
16788
|
const surfacePatches = [];
|
|
16790
16789
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16791
16790
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -16844,8 +16843,8 @@
|
|
|
16844
16843
|
transformedCorners[2].z +
|
|
16845
16844
|
transformedCorners[3].z) /
|
|
16846
16845
|
4,
|
|
16847
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
16848
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
16846
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
16847
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
16849
16848
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
16850
16849
|
});
|
|
16851
16850
|
}
|
|
@@ -16913,7 +16912,7 @@
|
|
|
16913
16912
|
*
|
|
16914
16913
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16915
16914
|
*/
|
|
16916
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16915
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16917
16916
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
16918
16917
|
if (tonalProgress < 0.16) {
|
|
16919
16918
|
return palette.highlight;
|
|
@@ -16959,7 +16958,7 @@
|
|
|
16959
16958
|
*
|
|
16960
16959
|
* @private helper of `octopus3d3AvatarVisual`
|
|
16961
16960
|
*/
|
|
16962
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
16961
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
16963
16962
|
x: 0.34,
|
|
16964
16963
|
y: -0.62,
|
|
16965
16964
|
z: 1,
|
|
@@ -16969,7 +16968,7 @@
|
|
|
16969
16968
|
*
|
|
16970
16969
|
* @private helper of `octopus3d3AvatarVisual`
|
|
16971
16970
|
*/
|
|
16972
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
16971
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
16973
16972
|
/**
|
|
16974
16973
|
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
16975
16974
|
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
@@ -17108,9 +17107,9 @@
|
|
|
17108
17107
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17109
17108
|
*/
|
|
17110
17109
|
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17111
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
17110
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
17112
17111
|
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
17113
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
17112
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
17114
17113
|
return {
|
|
17115
17114
|
centerLongitude: -Math.PI * 0.86 +
|
|
17116
17115
|
progress * Math.PI * 1.72 +
|
|
@@ -17177,13 +17176,13 @@
|
|
|
17177
17176
|
*
|
|
17178
17177
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17179
17178
|
*/
|
|
17180
|
-
const LATITUDE_PATCH_COUNT = 16;
|
|
17179
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
17181
17180
|
/**
|
|
17182
17181
|
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
17183
17182
|
*
|
|
17184
17183
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17185
17184
|
*/
|
|
17186
|
-
const LONGITUDE_PATCH_COUNT = 40;
|
|
17185
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
17187
17186
|
/**
|
|
17188
17187
|
* Resolves visible projected patches for the continuous octopus mesh.
|
|
17189
17188
|
*
|
|
@@ -17197,8 +17196,8 @@
|
|
|
17197
17196
|
*/
|
|
17198
17197
|
function resolveVisibleContinuousOctopusPatches(options) {
|
|
17199
17198
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17200
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
17201
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
17199
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
17200
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
17202
17201
|
const surfacePatches = [];
|
|
17203
17202
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17204
17203
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -17266,7 +17265,7 @@
|
|
|
17266
17265
|
transformedCorners[2].z +
|
|
17267
17266
|
transformedCorners[3].z) /
|
|
17268
17267
|
4,
|
|
17269
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
17268
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
17270
17269
|
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17271
17270
|
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
17272
17271
|
});
|
|
@@ -17300,9 +17299,9 @@
|
|
|
17300
17299
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17301
17300
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17302
17301
|
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
17303
|
-
const lowerBlend = smoothStep(0.38, 1, verticalProgress);
|
|
17304
|
-
const tipBlend = smoothStep(0.68, 1, verticalProgress);
|
|
17305
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
17302
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
17303
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
17304
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
17306
17305
|
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
17307
17306
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17308
17307
|
animationPhase * 0.6 +
|
|
@@ -17368,7 +17367,7 @@
|
|
|
17368
17367
|
let weightedDepthScale = 0;
|
|
17369
17368
|
let weightedPhase = 0;
|
|
17370
17369
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
17371
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
17370
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
17372
17371
|
const width = 0.2 * tentacleProfile.widthScale;
|
|
17373
17372
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
17374
17373
|
totalWeight += weight;
|
|
@@ -17408,7 +17407,7 @@
|
|
|
17408
17407
|
*/
|
|
17409
17408
|
function resolveContinuousLobeWave(options, longitude) {
|
|
17410
17409
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
17411
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
17410
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
17412
17411
|
animationPhase +
|
|
17413
17412
|
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
17414
17413
|
1) /
|
|
@@ -17562,7 +17561,7 @@
|
|
|
17562
17561
|
*
|
|
17563
17562
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17564
17563
|
*/
|
|
17565
|
-
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
17564
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
17566
17565
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
17567
17566
|
}
|
|
17568
17567
|
/**
|
|
@@ -17570,11 +17569,767 @@
|
|
|
17570
17569
|
*
|
|
17571
17570
|
* @private helper of `octopus3d3AvatarVisual`
|
|
17572
17571
|
*/
|
|
17573
|
-
function smoothStep(edgeStart, edgeEnd, value) {
|
|
17572
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
17574
17573
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
17575
17574
|
return progress * progress * (3 - 2 * progress);
|
|
17576
17575
|
}
|
|
17577
17576
|
|
|
17577
|
+
/* eslint-disable no-magic-numbers */
|
|
17578
|
+
/**
|
|
17579
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
17580
|
+
*
|
|
17581
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17582
|
+
*/
|
|
17583
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
17584
|
+
x: 0.32,
|
|
17585
|
+
y: -0.66,
|
|
17586
|
+
z: 1,
|
|
17587
|
+
});
|
|
17588
|
+
/**
|
|
17589
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
17590
|
+
*
|
|
17591
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17592
|
+
*/
|
|
17593
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
17594
|
+
x: -0.7,
|
|
17595
|
+
y: -0.18,
|
|
17596
|
+
z: 0.55,
|
|
17597
|
+
});
|
|
17598
|
+
/**
|
|
17599
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
17600
|
+
*
|
|
17601
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17602
|
+
*/
|
|
17603
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
17604
|
+
/**
|
|
17605
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
17606
|
+
*
|
|
17607
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17608
|
+
*/
|
|
17609
|
+
const SKIN_SPOT_COUNT = 14;
|
|
17610
|
+
/**
|
|
17611
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
17612
|
+
*
|
|
17613
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17614
|
+
*/
|
|
17615
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
17616
|
+
/**
|
|
17617
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
17618
|
+
*
|
|
17619
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17620
|
+
*/
|
|
17621
|
+
function getOctopus3d4StableState(createRandom) {
|
|
17622
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
17623
|
+
if (cached !== undefined) {
|
|
17624
|
+
return cached;
|
|
17625
|
+
}
|
|
17626
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
17627
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
17628
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
17629
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
17630
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
17631
|
+
const state = {
|
|
17632
|
+
morphologyProfile,
|
|
17633
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
17634
|
+
leftEyePhaseOffset,
|
|
17635
|
+
rightEyePhaseOffset,
|
|
17636
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
17637
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
17638
|
+
};
|
|
17639
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
17640
|
+
return state;
|
|
17641
|
+
}
|
|
17642
|
+
/**
|
|
17643
|
+
* Octopus 3D 4 avatar visual.
|
|
17644
|
+
*
|
|
17645
|
+
* @private built-in avatar visual
|
|
17646
|
+
*/
|
|
17647
|
+
const octopus3d4AvatarVisual = {
|
|
17648
|
+
id: 'octopus3d4',
|
|
17649
|
+
title: 'Octopus 3D 4',
|
|
17650
|
+
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.',
|
|
17651
|
+
isAnimated: true,
|
|
17652
|
+
supportsPointerTracking: true,
|
|
17653
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
17654
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
17655
|
+
const sceneCenterX = size * 0.5;
|
|
17656
|
+
const sceneCenterY = size * 0.535;
|
|
17657
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
17658
|
+
const meshCenter = {
|
|
17659
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
17660
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
17661
|
+
z: interaction.intensity * size * 0.02,
|
|
17662
|
+
};
|
|
17663
|
+
const rotationY = -0.08 +
|
|
17664
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
17665
|
+
interaction.bodyOffsetX * 0.24 +
|
|
17666
|
+
interaction.gazeX * 0.98;
|
|
17667
|
+
const rotationX = -0.07 +
|
|
17668
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
17669
|
+
interaction.bodyOffsetY * 0.08 -
|
|
17670
|
+
interaction.gazeY * 0.42;
|
|
17671
|
+
const surfaceOptions = {
|
|
17672
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
17673
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
17674
|
+
radiusZ: size *
|
|
17675
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
17676
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
17677
|
+
morphologyProfile,
|
|
17678
|
+
timeMs,
|
|
17679
|
+
animationPhase,
|
|
17680
|
+
tentacleProfiles,
|
|
17681
|
+
};
|
|
17682
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
17683
|
+
...surfaceOptions,
|
|
17684
|
+
center: meshCenter,
|
|
17685
|
+
rotationX,
|
|
17686
|
+
rotationY,
|
|
17687
|
+
sceneCenterX,
|
|
17688
|
+
sceneCenterY,
|
|
17689
|
+
size,
|
|
17690
|
+
palette,
|
|
17691
|
+
});
|
|
17692
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
17693
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
17694
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
17695
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
17696
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
17697
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
17698
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
17699
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
17700
|
+
drawAvatarFrame(context, size, palette);
|
|
17701
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
17702
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
17703
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
17704
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
17705
|
+
}
|
|
17706
|
+
drawBlobbySkinSpots({
|
|
17707
|
+
context,
|
|
17708
|
+
surfaceOptions,
|
|
17709
|
+
center: meshCenter,
|
|
17710
|
+
rotationX,
|
|
17711
|
+
rotationY,
|
|
17712
|
+
sceneCenterX,
|
|
17713
|
+
sceneCenterY,
|
|
17714
|
+
size,
|
|
17715
|
+
palette,
|
|
17716
|
+
skinSpots,
|
|
17717
|
+
});
|
|
17718
|
+
drawBlobbyContinuousCurrents({
|
|
17719
|
+
context,
|
|
17720
|
+
surfaceOptions,
|
|
17721
|
+
center: meshCenter,
|
|
17722
|
+
rotationX,
|
|
17723
|
+
rotationY,
|
|
17724
|
+
sceneCenterX,
|
|
17725
|
+
sceneCenterY,
|
|
17726
|
+
size,
|
|
17727
|
+
palette,
|
|
17728
|
+
morphologyProfile,
|
|
17729
|
+
timeMs,
|
|
17730
|
+
animationPhase,
|
|
17731
|
+
});
|
|
17732
|
+
drawBlobbyContinuousSuckers({
|
|
17733
|
+
context,
|
|
17734
|
+
surfaceOptions,
|
|
17735
|
+
center: meshCenter,
|
|
17736
|
+
rotationX,
|
|
17737
|
+
rotationY,
|
|
17738
|
+
sceneCenterX,
|
|
17739
|
+
sceneCenterY,
|
|
17740
|
+
size,
|
|
17741
|
+
palette,
|
|
17742
|
+
});
|
|
17743
|
+
drawBlobbyContinuousGloss({
|
|
17744
|
+
context,
|
|
17745
|
+
surfaceOptions,
|
|
17746
|
+
center: meshCenter,
|
|
17747
|
+
rotationX,
|
|
17748
|
+
rotationY,
|
|
17749
|
+
sceneCenterX,
|
|
17750
|
+
sceneCenterY,
|
|
17751
|
+
size,
|
|
17752
|
+
palette,
|
|
17753
|
+
});
|
|
17754
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17755
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17756
|
+
drawProjectedOrganicMouth(context, [
|
|
17757
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
17758
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
17759
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
17760
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
17761
|
+
},
|
|
17762
|
+
};
|
|
17763
|
+
/**
|
|
17764
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
17765
|
+
*
|
|
17766
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17767
|
+
*/
|
|
17768
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17769
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
17770
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
17771
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
17772
|
+
return {
|
|
17773
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
17774
|
+
progress * Math.PI * 1.8 +
|
|
17775
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
17776
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
17777
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
17778
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
17779
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
17780
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
17781
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17782
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17783
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
17784
|
+
};
|
|
17785
|
+
});
|
|
17786
|
+
}
|
|
17787
|
+
/**
|
|
17788
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
17789
|
+
*
|
|
17790
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17791
|
+
*/
|
|
17792
|
+
function createBlobbySkinSpots(createRandom) {
|
|
17793
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
17794
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
17795
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
17796
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
17797
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
17798
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
17799
|
+
}));
|
|
17800
|
+
}
|
|
17801
|
+
/**
|
|
17802
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
17803
|
+
*
|
|
17804
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17805
|
+
*/
|
|
17806
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
17807
|
+
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));
|
|
17808
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
17809
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
17810
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
17811
|
+
context.fillStyle = glowGradient;
|
|
17812
|
+
context.fillRect(0, 0, size, size);
|
|
17813
|
+
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);
|
|
17814
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
17815
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
17816
|
+
context.fillStyle = lowerGradient;
|
|
17817
|
+
context.fillRect(0, 0, size, size);
|
|
17818
|
+
}
|
|
17819
|
+
/**
|
|
17820
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
17821
|
+
*
|
|
17822
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17823
|
+
*/
|
|
17824
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
17825
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
17826
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
17827
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
17828
|
+
const ry = size * 0.062;
|
|
17829
|
+
context.save();
|
|
17830
|
+
context.translate(cx, cy);
|
|
17831
|
+
context.scale(1, ry / rx);
|
|
17832
|
+
const blurRadius = rx * 1.42;
|
|
17833
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
17834
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
17835
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
17836
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
17837
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
17838
|
+
context.fillStyle = shadowGradient;
|
|
17839
|
+
context.beginPath();
|
|
17840
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
17841
|
+
context.fill();
|
|
17842
|
+
context.restore();
|
|
17843
|
+
}
|
|
17844
|
+
/**
|
|
17845
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17846
|
+
*
|
|
17847
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17848
|
+
*/
|
|
17849
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
17850
|
+
/**
|
|
17851
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17852
|
+
*
|
|
17853
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17854
|
+
*/
|
|
17855
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
17856
|
+
/**
|
|
17857
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
17858
|
+
*
|
|
17859
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
17860
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
17861
|
+
* than re-evaluated for every patch corner.
|
|
17862
|
+
*
|
|
17863
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17864
|
+
*/
|
|
17865
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
17866
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17867
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
17868
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
17869
|
+
const surfacePatches = [];
|
|
17870
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17871
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
17872
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
17873
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
17874
|
+
}
|
|
17875
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17876
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
17877
|
+
}
|
|
17878
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
17879
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
17880
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
17881
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17882
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17883
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17884
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
17885
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
17886
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
17887
|
+
}
|
|
17888
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17889
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
17890
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
17891
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
17892
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
17893
|
+
}
|
|
17894
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
17895
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
17896
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
17897
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
17898
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
17899
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
17900
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
17901
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
17902
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
17903
|
+
}
|
|
17904
|
+
}
|
|
17905
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
17906
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
17907
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
17908
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
17909
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
17910
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
17911
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
17912
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17913
|
+
const transformedCorners = [
|
|
17914
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
17915
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
17916
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
17917
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
17918
|
+
];
|
|
17919
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
17920
|
+
if (surfaceNormal.z <= 0.006) {
|
|
17921
|
+
continue;
|
|
17922
|
+
}
|
|
17923
|
+
const projectedCorners = [
|
|
17924
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
17925
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
17926
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
17927
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
17928
|
+
];
|
|
17929
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
17930
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
17931
|
+
surfacePatches.push({
|
|
17932
|
+
corners: projectedCorners,
|
|
17933
|
+
averageDepth: (transformedCorners[0].z +
|
|
17934
|
+
transformedCorners[1].z +
|
|
17935
|
+
transformedCorners[2].z +
|
|
17936
|
+
transformedCorners[3].z) /
|
|
17937
|
+
4,
|
|
17938
|
+
lightIntensity,
|
|
17939
|
+
rimLightIntensity,
|
|
17940
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17941
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
17942
|
+
});
|
|
17943
|
+
}
|
|
17944
|
+
}
|
|
17945
|
+
return surfacePatches;
|
|
17946
|
+
}
|
|
17947
|
+
/**
|
|
17948
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
17949
|
+
*
|
|
17950
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17951
|
+
*/
|
|
17952
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
17953
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
17954
|
+
}
|
|
17955
|
+
/**
|
|
17956
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
17957
|
+
*
|
|
17958
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
17959
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
17960
|
+
* of `latitudePatchCount * 4` times.
|
|
17961
|
+
*
|
|
17962
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17963
|
+
*/
|
|
17964
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
17965
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
17966
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17967
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17968
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
17969
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
17970
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
17971
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
17972
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
17973
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17974
|
+
animationPhase * 0.6 +
|
|
17975
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
17976
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
17977
|
+
(0.32 + lowerBlend * 0.7);
|
|
17978
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
17979
|
+
lowerBlend *
|
|
17980
|
+
tentacleInfluence.core *
|
|
17981
|
+
tentacleInfluence.swayScale;
|
|
17982
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
17983
|
+
lowerBlend *
|
|
17984
|
+
tentacleInfluence.core *
|
|
17985
|
+
tentacleInfluence.swayScale *
|
|
17986
|
+
0.42;
|
|
17987
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
17988
|
+
tipBlend *
|
|
17989
|
+
tentacleInfluence.core *
|
|
17990
|
+
tentacleInfluence.curlScale *
|
|
17991
|
+
0.36;
|
|
17992
|
+
const horizontalScale = 1.06 +
|
|
17993
|
+
mantleRipple +
|
|
17994
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
17995
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
17996
|
+
upperBlend * 0.08;
|
|
17997
|
+
const depthScale = 1.08 +
|
|
17998
|
+
upperBlend * 0.16 +
|
|
17999
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
18000
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
18001
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
18002
|
+
const tentacleTubeRadius = lowerBlend *
|
|
18003
|
+
tentacleInfluence.core *
|
|
18004
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
18005
|
+
radiusX;
|
|
18006
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
18007
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
18008
|
+
const lowerDrop = lowerBlend *
|
|
18009
|
+
radiusY *
|
|
18010
|
+
(0.2 +
|
|
18011
|
+
tentacleInfluence.core *
|
|
18012
|
+
(0.42 +
|
|
18013
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
18014
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
18015
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
18016
|
+
return {
|
|
18017
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
18018
|
+
combinedTentacleSway +
|
|
18019
|
+
tentacleCurl * radiusX * 0.18,
|
|
18020
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
18021
|
+
upperBlend * radiusY * 0.12 +
|
|
18022
|
+
lowerDrop +
|
|
18023
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
18024
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
18025
|
+
lowerBlend *
|
|
18026
|
+
tipBlend *
|
|
18027
|
+
tentacleInfluence.core *
|
|
18028
|
+
radiusY *
|
|
18029
|
+
0.04,
|
|
18030
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
18031
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
18032
|
+
lowerBlend *
|
|
18033
|
+
tentacleInfluence.core *
|
|
18034
|
+
radiusZ *
|
|
18035
|
+
0.044 +
|
|
18036
|
+
tentacleCurl * radiusZ * 0.14,
|
|
18037
|
+
};
|
|
18038
|
+
}
|
|
18039
|
+
/**
|
|
18040
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
18041
|
+
*
|
|
18042
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18043
|
+
*/
|
|
18044
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
18045
|
+
let totalWeight = 0;
|
|
18046
|
+
let weightedSin = 0;
|
|
18047
|
+
let weightedCos = 0;
|
|
18048
|
+
let weightedWidthScale = 0;
|
|
18049
|
+
let weightedLengthScale = 0;
|
|
18050
|
+
let weightedSwayScale = 0;
|
|
18051
|
+
let weightedDepthScale = 0;
|
|
18052
|
+
let weightedCurlScale = 0;
|
|
18053
|
+
let weightedPrimaryPhase = 0;
|
|
18054
|
+
let weightedSecondaryPhase = 0;
|
|
18055
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
18056
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
18057
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
18058
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
18059
|
+
totalWeight += weight;
|
|
18060
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
18061
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
18062
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
18063
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
18064
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
18065
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
18066
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
18067
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
18068
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
18069
|
+
}
|
|
18070
|
+
if (totalWeight < 0.0001) {
|
|
18071
|
+
return {
|
|
18072
|
+
core: 0,
|
|
18073
|
+
centerLongitude: longitude,
|
|
18074
|
+
widthScale: 1,
|
|
18075
|
+
lengthScale: 1,
|
|
18076
|
+
swayScale: 1,
|
|
18077
|
+
depthScale: 1,
|
|
18078
|
+
curlScale: 1,
|
|
18079
|
+
primaryPhase: 0,
|
|
18080
|
+
secondaryPhase: 0,
|
|
18081
|
+
};
|
|
18082
|
+
}
|
|
18083
|
+
return {
|
|
18084
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
18085
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
18086
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
18087
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
18088
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
18089
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
18090
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
18091
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
18092
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
18093
|
+
};
|
|
18094
|
+
}
|
|
18095
|
+
/**
|
|
18096
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
18097
|
+
*
|
|
18098
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18099
|
+
*/
|
|
18100
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
18101
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
18102
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
18103
|
+
animationPhase +
|
|
18104
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
18105
|
+
1) /
|
|
18106
|
+
2);
|
|
18107
|
+
}
|
|
18108
|
+
/**
|
|
18109
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
18110
|
+
*
|
|
18111
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18112
|
+
*/
|
|
18113
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
18114
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
18115
|
+
if (tonalProgress < 0.12) {
|
|
18116
|
+
return palette.highlight;
|
|
18117
|
+
}
|
|
18118
|
+
if (tonalProgress < 0.3) {
|
|
18119
|
+
return palette.secondary;
|
|
18120
|
+
}
|
|
18121
|
+
if (tonalProgress < 0.72) {
|
|
18122
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
18123
|
+
}
|
|
18124
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
18125
|
+
}
|
|
18126
|
+
/**
|
|
18127
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
18128
|
+
*
|
|
18129
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18130
|
+
*/
|
|
18131
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
18132
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
18133
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
18134
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
18135
|
+
}
|
|
18136
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
18137
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
18138
|
+
}
|
|
18139
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
18140
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
18141
|
+
}
|
|
18142
|
+
context.save();
|
|
18143
|
+
context.beginPath();
|
|
18144
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
18145
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
18146
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
18147
|
+
}
|
|
18148
|
+
context.closePath();
|
|
18149
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
18150
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
18151
|
+
context.lineJoin = 'round';
|
|
18152
|
+
context.stroke();
|
|
18153
|
+
context.restore();
|
|
18154
|
+
}
|
|
18155
|
+
/**
|
|
18156
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
18157
|
+
*
|
|
18158
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18159
|
+
*/
|
|
18160
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
18161
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
18162
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
18163
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
18164
|
+
context.save();
|
|
18165
|
+
context.lineCap = 'round';
|
|
18166
|
+
context.lineJoin = 'round';
|
|
18167
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
18168
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
18169
|
+
const projectedPoints = [];
|
|
18170
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
18171
|
+
const progress = sampleIndex / 8;
|
|
18172
|
+
const latitude = -0.48 + progress * 0.78;
|
|
18173
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
18174
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
18175
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
18176
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
18177
|
+
}
|
|
18178
|
+
}
|
|
18179
|
+
if (projectedPoints.length < 3) {
|
|
18180
|
+
continue;
|
|
18181
|
+
}
|
|
18182
|
+
context.beginPath();
|
|
18183
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
18184
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
18185
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
18186
|
+
}
|
|
18187
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
18188
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
18189
|
+
context.stroke();
|
|
18190
|
+
}
|
|
18191
|
+
context.restore();
|
|
18192
|
+
}
|
|
18193
|
+
/**
|
|
18194
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
18195
|
+
*
|
|
18196
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18197
|
+
*/
|
|
18198
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
18199
|
+
const { surfaceOptions, size } = options;
|
|
18200
|
+
const { timeMs } = surfaceOptions;
|
|
18201
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
18202
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
18203
|
+
continue;
|
|
18204
|
+
}
|
|
18205
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
18206
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
18207
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
18208
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
18209
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
18210
|
+
...options,
|
|
18211
|
+
latitude,
|
|
18212
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
18213
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
18214
|
+
});
|
|
18215
|
+
}
|
|
18216
|
+
}
|
|
18217
|
+
}
|
|
18218
|
+
/**
|
|
18219
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
18220
|
+
*
|
|
18221
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18222
|
+
*/
|
|
18223
|
+
function drawBlobbySkinSpots(options) {
|
|
18224
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
18225
|
+
for (const skinSpot of skinSpots) {
|
|
18226
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
18227
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18228
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
18229
|
+
continue;
|
|
18230
|
+
}
|
|
18231
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18232
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
18233
|
+
context.save();
|
|
18234
|
+
context.beginPath();
|
|
18235
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
18236
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
18237
|
+
context.fill();
|
|
18238
|
+
context.restore();
|
|
18239
|
+
}
|
|
18240
|
+
}
|
|
18241
|
+
/**
|
|
18242
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
18243
|
+
*
|
|
18244
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18245
|
+
*/
|
|
18246
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
18247
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
18248
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
18249
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
18250
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
18251
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18252
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
18253
|
+
return;
|
|
18254
|
+
}
|
|
18255
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18256
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18257
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18258
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
18259
|
+
radiusScale *
|
|
18260
|
+
0.78, size * 0.003, size * 0.02);
|
|
18261
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
18262
|
+
radiusScale *
|
|
18263
|
+
0.54, size * 0.0024, size * 0.015);
|
|
18264
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
18265
|
+
context.save();
|
|
18266
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
18267
|
+
context.rotate(rotation);
|
|
18268
|
+
context.beginPath();
|
|
18269
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
18270
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
18271
|
+
context.fill();
|
|
18272
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
18273
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
18274
|
+
context.stroke();
|
|
18275
|
+
context.restore();
|
|
18276
|
+
}
|
|
18277
|
+
/**
|
|
18278
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
18279
|
+
*
|
|
18280
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18281
|
+
*/
|
|
18282
|
+
function drawBlobbyContinuousGloss(options) {
|
|
18283
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
18284
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
18285
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
18286
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
18287
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
18288
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18289
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
18290
|
+
return;
|
|
18291
|
+
}
|
|
18292
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18293
|
+
const glossRadius = size * 0.058;
|
|
18294
|
+
context.save();
|
|
18295
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
18296
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
18297
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
18298
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
18299
|
+
context.fillStyle = glossGradient;
|
|
18300
|
+
context.beginPath();
|
|
18301
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
18302
|
+
context.fill();
|
|
18303
|
+
context.restore();
|
|
18304
|
+
}
|
|
18305
|
+
/**
|
|
18306
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
18307
|
+
*
|
|
18308
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18309
|
+
*/
|
|
18310
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
18311
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
18312
|
+
}
|
|
18313
|
+
/**
|
|
18314
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
18315
|
+
*
|
|
18316
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18317
|
+
*/
|
|
18318
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
18319
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
18320
|
+
return progress * progress * (3 - 2 * progress);
|
|
18321
|
+
}
|
|
18322
|
+
/**
|
|
18323
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
18324
|
+
*
|
|
18325
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18326
|
+
*/
|
|
18327
|
+
function formatAlphaHex(opacity) {
|
|
18328
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
18329
|
+
.toString(16)
|
|
18330
|
+
.padStart(2, '0');
|
|
18331
|
+
}
|
|
18332
|
+
|
|
17578
18333
|
/* eslint-disable no-magic-numbers */
|
|
17579
18334
|
/**
|
|
17580
18335
|
* Octopus avatar visual.
|
|
@@ -18346,6 +19101,7 @@
|
|
|
18346
19101
|
octopus3dAvatarVisual,
|
|
18347
19102
|
octopus3d2AvatarVisual,
|
|
18348
19103
|
octopus3d3AvatarVisual,
|
|
19104
|
+
octopus3d4AvatarVisual,
|
|
18349
19105
|
asciiOctopusAvatarVisual,
|
|
18350
19106
|
minecraftAvatarVisual,
|
|
18351
19107
|
minecraft2AvatarVisual,
|