@promptbook/browser 0.112.0-99 → 0.113.0-0
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/README.md +76 -44
- package/esm/index.es.js +1805 -103
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/components.index.d.ts +4 -0
- package/esm/src/_packages/core.index.d.ts +22 -2
- package/esm/src/_packages/node.index.d.ts +40 -0
- package/esm/src/_packages/types.index.d.ts +20 -0
- package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/esm/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/esm/src/book-3.0/CliAgent.d.ts +66 -0
- package/esm/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/LiteAgent.d.ts +68 -0
- package/esm/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/esm/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/esm/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/esm/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
- package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/esm/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/esm/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/esm/src/cli/cli-commands/agent.d.ts +14 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
- package/esm/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/esm/src/commitments/index.d.ts +2 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/esm/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -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 +1808 -104
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/components.index.d.ts +4 -0
- package/umd/src/_packages/core.index.d.ts +22 -2
- package/umd/src/_packages/node.index.d.ts +40 -0
- package/umd/src/_packages/types.index.d.ts +20 -0
- package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/umd/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/umd/src/book-3.0/CliAgent.d.ts +66 -0
- package/umd/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/LiteAgent.d.ts +68 -0
- package/umd/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/umd/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/umd/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/umd/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
- package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/umd/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/umd/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/umd/src/cli/cli-commands/agent.d.ts +14 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
- package/umd/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/umd/src/commitments/index.d.ts +2 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/umd/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -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
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { spaceTrim as spaceTrim$1 } from 'spacetrim';
|
|
2
2
|
import { Registration } from 'destroyable';
|
|
3
3
|
import { BehaviorSubject, Subject } from 'rxjs';
|
|
4
|
-
import
|
|
5
|
-
import { SHA256 } from 'crypto-js';
|
|
4
|
+
import CryptoJS from 'crypto-js';
|
|
6
5
|
import hexEncoder from 'crypto-js/enc-hex';
|
|
7
6
|
import { basename, join, dirname, isAbsolute } from 'path';
|
|
7
|
+
import 'crypto';
|
|
8
8
|
import moment from 'moment';
|
|
9
9
|
import { lookup, extension } from 'mime-types';
|
|
10
10
|
import { forTime } from 'waitasecond';
|
|
11
11
|
import sha256 from 'crypto-js/sha256';
|
|
12
|
-
import
|
|
12
|
+
import papaparse from 'papaparse';
|
|
13
13
|
import { fileSearchTool, tool, Agent as Agent$1, webSearchTool, run, setDefaultOpenAIClient, setDefaultOpenAIKey } from '@openai/agents';
|
|
14
14
|
import colors from 'colors';
|
|
15
15
|
import Bottleneck from 'bottleneck';
|
|
@@ -29,7 +29,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
29
29
|
* @generated
|
|
30
30
|
* @see https://github.com/webgptorg/promptbook
|
|
31
31
|
*/
|
|
32
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.
|
|
32
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.113.0-0';
|
|
33
33
|
/**
|
|
34
34
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
35
35
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -2332,7 +2332,7 @@ function valueToString(value) {
|
|
|
2332
2332
|
* @public exported from `@promptbook/utils`
|
|
2333
2333
|
*/
|
|
2334
2334
|
function computeHash(value) {
|
|
2335
|
-
return SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
|
|
2335
|
+
return CryptoJS.SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
|
|
2336
2336
|
}
|
|
2337
2337
|
// TODO: [🥬][🥬] Use this ACRY
|
|
2338
2338
|
|
|
@@ -2605,6 +2605,11 @@ function isValidUrl(url) {
|
|
|
2605
2605
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
2606
2606
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
2607
2607
|
*
|
|
2608
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
2609
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
2610
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
2611
|
+
* outbound request.
|
|
2612
|
+
*
|
|
2608
2613
|
* @public exported from `@promptbook/utils`
|
|
2609
2614
|
*/
|
|
2610
2615
|
function isValidAgentUrl(url) {
|
|
@@ -2618,12 +2623,6 @@ function isValidAgentUrl(url) {
|
|
|
2618
2623
|
// TODO: [🐠]
|
|
2619
2624
|
return false;
|
|
2620
2625
|
}
|
|
2621
|
-
/*
|
|
2622
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
2623
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
2624
|
-
return false;
|
|
2625
|
-
}
|
|
2626
|
-
*/
|
|
2627
2626
|
return true;
|
|
2628
2627
|
}
|
|
2629
2628
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -2799,8 +2798,11 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
2799
2798
|
if (!trimmedContent) {
|
|
2800
2799
|
return requirements;
|
|
2801
2800
|
}
|
|
2802
|
-
|
|
2803
|
-
|
|
2801
|
+
const goalSection = spaceTrim$1((block) => `
|
|
2802
|
+
## Goal
|
|
2803
|
+
|
|
2804
|
+
${block(trimmedContent)}
|
|
2805
|
+
`);
|
|
2804
2806
|
const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
|
|
2805
2807
|
return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
|
|
2806
2808
|
}
|
|
@@ -3694,8 +3696,11 @@ class LanguageCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
3694
3696
|
if (!trimmedContent) {
|
|
3695
3697
|
return requirements;
|
|
3696
3698
|
}
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
+
const languageSection = spaceTrim$1((block) => `
|
|
3700
|
+
## Language
|
|
3701
|
+
|
|
3702
|
+
- Your language is ${block(trimmedContent)}
|
|
3703
|
+
`);
|
|
3699
3704
|
return this.appendToSystemMessage(requirements, languageSection, '\n\n');
|
|
3700
3705
|
}
|
|
3701
3706
|
}
|
|
@@ -6161,21 +6166,22 @@ function getPointBounds(points) {
|
|
|
6161
6166
|
* @private helper of `fractalAvatarVisual`
|
|
6162
6167
|
*/
|
|
6163
6168
|
function drawDragonCurveLayer(context, points, options) {
|
|
6164
|
-
const {
|
|
6169
|
+
const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
|
|
6165
6170
|
const firstPoint = points[0];
|
|
6166
6171
|
const lastPoint = points[points.length - 1];
|
|
6167
6172
|
const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
|
|
6168
6173
|
ribbonGradient.addColorStop(0, `${primaryColor}f2`);
|
|
6169
6174
|
ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
|
|
6170
6175
|
ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
|
|
6176
|
+
// Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
|
|
6177
|
+
// context.filter blur, which triggers a costly software rasterization pass every frame.
|
|
6171
6178
|
context.save();
|
|
6172
6179
|
context.beginPath();
|
|
6173
6180
|
tracePolyline(context, points);
|
|
6174
|
-
context.strokeStyle = `${shadowColor}
|
|
6175
|
-
context.lineWidth = strokeWidth *
|
|
6181
|
+
context.strokeStyle = `${shadowColor}48`;
|
|
6182
|
+
context.lineWidth = strokeWidth * 4.5;
|
|
6176
6183
|
context.lineJoin = 'round';
|
|
6177
6184
|
context.lineCap = 'round';
|
|
6178
|
-
context.filter = `blur(${size * 0.022}px)`;
|
|
6179
6185
|
context.stroke();
|
|
6180
6186
|
context.restore();
|
|
6181
6187
|
context.beginPath();
|
|
@@ -6637,7 +6643,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
|
|
|
6637
6643
|
*
|
|
6638
6644
|
* @private helper of `minecraft2AvatarVisual`
|
|
6639
6645
|
*/
|
|
6640
|
-
const LIGHT_DIRECTION$
|
|
6646
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
6641
6647
|
x: 0.4,
|
|
6642
6648
|
y: -0.65,
|
|
6643
6649
|
z: 0.92,
|
|
@@ -6759,11 +6765,23 @@ function drawMinecraftBackdrop(context, size, palette, sceneCenterX, spotlightY,
|
|
|
6759
6765
|
* @private helper of `minecraft2AvatarVisual`
|
|
6760
6766
|
*/
|
|
6761
6767
|
function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
|
|
6768
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.03;
|
|
6769
|
+
const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
|
|
6770
|
+
const rx = size * (0.16 + interaction.intensity * 0.015);
|
|
6771
|
+
const ry = size * 0.055;
|
|
6772
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
6762
6773
|
context.save();
|
|
6763
|
-
context.
|
|
6764
|
-
context.
|
|
6774
|
+
context.translate(cx, cy);
|
|
6775
|
+
context.scale(1, ry / rx);
|
|
6776
|
+
const blurRadius = rx * 1.4;
|
|
6777
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
6778
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
6779
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
6780
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
6781
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
6782
|
+
context.fillStyle = shadowGradient;
|
|
6765
6783
|
context.beginPath();
|
|
6766
|
-
context.
|
|
6784
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
6767
6785
|
context.fill();
|
|
6768
6786
|
context.restore();
|
|
6769
6787
|
}
|
|
@@ -6849,7 +6867,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
|
|
|
6849
6867
|
corners: projectedCorners,
|
|
6850
6868
|
texture: faceDefinition.texture,
|
|
6851
6869
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
6852
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
6870
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
6853
6871
|
outlineColor: cuboid.outlineColor,
|
|
6854
6872
|
};
|
|
6855
6873
|
});
|
|
@@ -6987,13 +7005,27 @@ const minecraftAvatarVisual = {
|
|
|
6987
7005
|
spotlight.addColorStop(1, `${palette.highlight}00`);
|
|
6988
7006
|
context.fillStyle = spotlight;
|
|
6989
7007
|
context.fillRect(0, 0, size, size);
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
7008
|
+
{
|
|
7009
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
7010
|
+
const cx = size * 0.5;
|
|
7011
|
+
const cy = size * 0.86;
|
|
7012
|
+
const rx = size * 0.2;
|
|
7013
|
+
const ry = size * 0.06;
|
|
7014
|
+
const blurRadius = rx * 1.4;
|
|
7015
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
7016
|
+
shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
|
|
7017
|
+
shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
|
|
7018
|
+
shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
|
|
7019
|
+
shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
7020
|
+
context.save();
|
|
7021
|
+
context.translate(cx, cy);
|
|
7022
|
+
context.scale(1, ry / rx);
|
|
7023
|
+
context.fillStyle = shadowGradient;
|
|
7024
|
+
context.beginPath();
|
|
7025
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
7026
|
+
context.fill();
|
|
7027
|
+
context.restore();
|
|
7028
|
+
}
|
|
6997
7029
|
drawVoxelCuboid(context, {
|
|
6998
7030
|
x: bodyX,
|
|
6999
7031
|
y: bodyY,
|
|
@@ -7522,7 +7554,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
|
|
|
7522
7554
|
*
|
|
7523
7555
|
* @private helper of `octopus3AvatarVisual`
|
|
7524
7556
|
*/
|
|
7525
|
-
function formatAlphaHex$
|
|
7557
|
+
function formatAlphaHex$2(opacity) {
|
|
7526
7558
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
7527
7559
|
.toString(16)
|
|
7528
7560
|
.padStart(2, '0');
|
|
@@ -7901,7 +7933,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
|
|
|
7901
7933
|
context.beginPath();
|
|
7902
7934
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
7903
7935
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
7904
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
7936
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
7905
7937
|
context.lineWidth = radiusX * 0.08;
|
|
7906
7938
|
context.lineCap = 'round';
|
|
7907
7939
|
context.stroke();
|
|
@@ -7982,7 +8014,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
|
|
|
7982
8014
|
context.beginPath();
|
|
7983
8015
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
7984
8016
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
7985
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
8017
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
7986
8018
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
7987
8019
|
context.lineCap = 'round';
|
|
7988
8020
|
context.stroke();
|
|
@@ -8028,7 +8060,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
|
|
|
8028
8060
|
*
|
|
8029
8061
|
* @private helper of the 3D octopus avatar visuals
|
|
8030
8062
|
*/
|
|
8031
|
-
function formatAlphaHex(opacity) {
|
|
8063
|
+
function formatAlphaHex$1(opacity) {
|
|
8032
8064
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
8033
8065
|
.toString(16)
|
|
8034
8066
|
.padStart(2, '0');
|
|
@@ -8040,11 +8072,40 @@ function formatAlphaHex(opacity) {
|
|
|
8040
8072
|
*
|
|
8041
8073
|
* @private helper of `octopus3dAvatarVisual`
|
|
8042
8074
|
*/
|
|
8043
|
-
const LIGHT_DIRECTION$
|
|
8075
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
8044
8076
|
x: 0.48,
|
|
8045
8077
|
y: -0.62,
|
|
8046
8078
|
z: 0.94,
|
|
8047
8079
|
});
|
|
8080
|
+
/**
|
|
8081
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
8082
|
+
*
|
|
8083
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
8084
|
+
*/
|
|
8085
|
+
const octopus3dStableStateCache = new WeakMap();
|
|
8086
|
+
/**
|
|
8087
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
8088
|
+
*
|
|
8089
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
8090
|
+
*/
|
|
8091
|
+
function getOctopus3dStableState(createRandom) {
|
|
8092
|
+
const cached = octopus3dStableStateCache.get(createRandom);
|
|
8093
|
+
if (cached !== undefined) {
|
|
8094
|
+
return cached;
|
|
8095
|
+
}
|
|
8096
|
+
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
8097
|
+
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
8098
|
+
const leftEyePhaseOffset = eyeRandom() * 0.6;
|
|
8099
|
+
const rightEyePhaseOffset = eyeRandom() * 0.6;
|
|
8100
|
+
const state = {
|
|
8101
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
8102
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
8103
|
+
leftEyePhaseOffset,
|
|
8104
|
+
rightEyePhaseOffset,
|
|
8105
|
+
};
|
|
8106
|
+
octopus3dStableStateCache.set(createRandom, state);
|
|
8107
|
+
return state;
|
|
8108
|
+
}
|
|
8048
8109
|
/**
|
|
8049
8110
|
* Proper 3D Octopus visual built from projected organic meshes and tentacles.
|
|
8050
8111
|
*
|
|
@@ -8057,10 +8118,7 @@ const octopus3dAvatarVisual = {
|
|
|
8057
8118
|
isAnimated: true,
|
|
8058
8119
|
supportsPointerTracking: true,
|
|
8059
8120
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
8060
|
-
const morphologyProfile =
|
|
8061
|
-
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
8062
|
-
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
8063
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
8121
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
|
|
8064
8122
|
const sceneCenterX = size * 0.5;
|
|
8065
8123
|
const sceneCenterY = size * 0.56;
|
|
8066
8124
|
const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
|
|
@@ -8157,12 +8215,12 @@ const octopus3dAvatarVisual = {
|
|
|
8157
8215
|
x: -faceEyeSpacing,
|
|
8158
8216
|
y: faceEyeYOffset,
|
|
8159
8217
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
|
|
8160
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
8218
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8161
8219
|
drawProjectedOrganicEye(context, {
|
|
8162
8220
|
x: faceEyeSpacing,
|
|
8163
8221
|
y: faceEyeYOffset,
|
|
8164
8222
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
|
|
8165
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 +
|
|
8223
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8166
8224
|
drawProjectedOrganicMouth(context, [
|
|
8167
8225
|
{
|
|
8168
8226
|
x: -mouthHalfWidth,
|
|
@@ -8206,14 +8264,28 @@ function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCent
|
|
|
8206
8264
|
/**
|
|
8207
8265
|
* Draws the soft ground shadow below the octopus.
|
|
8208
8266
|
*
|
|
8267
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
8268
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
8269
|
+
*
|
|
8209
8270
|
* @private helper of `octopus3dAvatarVisual`
|
|
8210
8271
|
*/
|
|
8211
8272
|
function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
|
|
8273
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.04;
|
|
8274
|
+
const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
|
|
8275
|
+
const rx = size * (0.18 + interaction.intensity * 0.02);
|
|
8276
|
+
const ry = size * 0.06;
|
|
8212
8277
|
context.save();
|
|
8213
|
-
context.
|
|
8214
|
-
context.
|
|
8278
|
+
context.translate(cx, cy);
|
|
8279
|
+
context.scale(1, ry / rx);
|
|
8280
|
+
const blurRadius = rx * 1.4;
|
|
8281
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
8282
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
8283
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
8284
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
8285
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
8286
|
+
context.fillStyle = shadowGradient;
|
|
8215
8287
|
context.beginPath();
|
|
8216
|
-
context.
|
|
8288
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
8217
8289
|
context.fill();
|
|
8218
8290
|
context.restore();
|
|
8219
8291
|
}
|
|
@@ -8250,7 +8322,7 @@ function resolveVisibleEllipsoidPatches(options) {
|
|
|
8250
8322
|
corners: projectedCorners,
|
|
8251
8323
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
8252
8324
|
transformedCorners.length,
|
|
8253
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
8325
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
8254
8326
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
8255
8327
|
outlineColor,
|
|
8256
8328
|
});
|
|
@@ -8439,11 +8511,40 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
|
|
|
8439
8511
|
*
|
|
8440
8512
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8441
8513
|
*/
|
|
8442
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
8514
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
8443
8515
|
x: 0.38,
|
|
8444
8516
|
y: -0.6,
|
|
8445
8517
|
z: 0.98,
|
|
8446
8518
|
});
|
|
8519
|
+
/**
|
|
8520
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
8521
|
+
*
|
|
8522
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8523
|
+
*/
|
|
8524
|
+
const octopus3d2StableStateCache = new WeakMap();
|
|
8525
|
+
/**
|
|
8526
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
8527
|
+
*
|
|
8528
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8529
|
+
*/
|
|
8530
|
+
function getOctopus3d2StableState(createRandom) {
|
|
8531
|
+
const cached = octopus3d2StableStateCache.get(createRandom);
|
|
8532
|
+
if (cached !== undefined) {
|
|
8533
|
+
return cached;
|
|
8534
|
+
}
|
|
8535
|
+
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
8536
|
+
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
8537
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
8538
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
8539
|
+
const state = {
|
|
8540
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
8541
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
8542
|
+
leftEyePhaseOffset,
|
|
8543
|
+
rightEyePhaseOffset,
|
|
8544
|
+
};
|
|
8545
|
+
octopus3d2StableStateCache.set(createRandom, state);
|
|
8546
|
+
return state;
|
|
8547
|
+
}
|
|
8447
8548
|
/**
|
|
8448
8549
|
* Octopus 3D 2 avatar visual.
|
|
8449
8550
|
*
|
|
@@ -8456,10 +8557,7 @@ const octopus3d2AvatarVisual = {
|
|
|
8456
8557
|
isAnimated: true,
|
|
8457
8558
|
supportsPointerTracking: true,
|
|
8458
8559
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
8459
|
-
const morphologyProfile =
|
|
8460
|
-
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
8461
|
-
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
8462
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
8560
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
|
|
8463
8561
|
const sceneCenterX = size * 0.5;
|
|
8464
8562
|
const sceneCenterY = size * 0.575;
|
|
8465
8563
|
const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
|
|
@@ -8512,8 +8610,8 @@ const octopus3d2AvatarVisual = {
|
|
|
8512
8610
|
const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
|
|
8513
8611
|
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
8514
8612
|
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
8515
|
-
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
8516
|
-
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 +
|
|
8613
|
+
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8614
|
+
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8517
8615
|
drawProjectedOrganicMouth(context, [
|
|
8518
8616
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
8519
8617
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
@@ -8542,54 +8640,118 @@ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, scene
|
|
|
8542
8640
|
/**
|
|
8543
8641
|
* Draws the soft floor shadow that anchors the single mesh in the frame.
|
|
8544
8642
|
*
|
|
8643
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
8644
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
8645
|
+
*
|
|
8545
8646
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8546
8647
|
*/
|
|
8547
8648
|
function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
8649
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
8650
|
+
const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
|
|
8651
|
+
const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
|
|
8652
|
+
const ry = size * 0.062;
|
|
8548
8653
|
context.save();
|
|
8549
|
-
context.
|
|
8550
|
-
context.
|
|
8654
|
+
context.translate(cx, cy);
|
|
8655
|
+
context.scale(1, ry / rx);
|
|
8656
|
+
const blurRadius = rx * 1.4;
|
|
8657
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
8658
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
8659
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
8660
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
8661
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
8662
|
+
context.fillStyle = shadowGradient;
|
|
8551
8663
|
context.beginPath();
|
|
8552
|
-
context.
|
|
8664
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
8553
8665
|
context.fill();
|
|
8554
8666
|
context.restore();
|
|
8555
8667
|
}
|
|
8668
|
+
/**
|
|
8669
|
+
* Number of latitude segments used by the single blobby octopus mesh.
|
|
8670
|
+
*
|
|
8671
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8672
|
+
*/
|
|
8673
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
8674
|
+
/**
|
|
8675
|
+
* Number of longitude segments used by the single blobby octopus mesh.
|
|
8676
|
+
*
|
|
8677
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8678
|
+
*/
|
|
8679
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
8556
8680
|
/**
|
|
8557
8681
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
8558
8682
|
*
|
|
8683
|
+
* Within a single frame, mesh corner samples and longitude-only lobe-wave values are
|
|
8684
|
+
* quantized to the patch grid and computed once each rather than re-evaluated for every
|
|
8685
|
+
* patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
|
|
8686
|
+
* `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
|
|
8687
|
+
* corners are shared between neighboring patches.
|
|
8688
|
+
*
|
|
8559
8689
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8560
8690
|
*/
|
|
8561
8691
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
8562
8692
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
8563
|
-
const latitudePatchCount =
|
|
8564
|
-
const longitudePatchCount =
|
|
8693
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
8694
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
8565
8695
|
const surfacePatches = [];
|
|
8696
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
8697
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
8698
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
8699
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
8700
|
+
}
|
|
8701
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
8702
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
8703
|
+
}
|
|
8704
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
8705
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
8706
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
|
|
8707
|
+
}
|
|
8708
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
8709
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
8710
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
8711
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
8712
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
8713
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
8714
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
8715
|
+
const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
8716
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
8717
|
+
}
|
|
8718
|
+
}
|
|
8566
8719
|
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
8567
|
-
const startLatitude =
|
|
8568
|
-
const endLatitude =
|
|
8720
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
8721
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
8569
8722
|
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
8570
8723
|
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
8724
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
8725
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
8571
8726
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
8572
|
-
const startLongitude =
|
|
8573
|
-
const endLongitude =
|
|
8727
|
+
const startLongitude = longitudeBoundaries[longitudeIndex];
|
|
8728
|
+
const endLongitude = longitudeBoundaries[longitudeIndex + 1];
|
|
8574
8729
|
const centerLongitude = (startLongitude + endLongitude) / 2;
|
|
8575
|
-
const
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8730
|
+
const transformedCorners = [
|
|
8731
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
8732
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
8733
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
8734
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
8580
8735
|
];
|
|
8581
|
-
const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
|
|
8582
8736
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
8583
8737
|
if (surfaceNormal.z <= 0.01) {
|
|
8584
8738
|
continue;
|
|
8585
8739
|
}
|
|
8586
|
-
const projectedCorners =
|
|
8740
|
+
const projectedCorners = [
|
|
8741
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
8742
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
8743
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
8744
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
8745
|
+
];
|
|
8587
8746
|
surfacePatches.push({
|
|
8588
8747
|
corners: projectedCorners,
|
|
8589
|
-
averageDepth: transformedCorners.
|
|
8590
|
-
transformedCorners.
|
|
8591
|
-
|
|
8592
|
-
|
|
8748
|
+
averageDepth: (transformedCorners[0].z +
|
|
8749
|
+
transformedCorners[1].z +
|
|
8750
|
+
transformedCorners[2].z +
|
|
8751
|
+
transformedCorners[3].z) /
|
|
8752
|
+
4,
|
|
8753
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
8754
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
8593
8755
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
8594
8756
|
});
|
|
8595
8757
|
}
|
|
@@ -8605,12 +8767,21 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
8605
8767
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8606
8768
|
*/
|
|
8607
8769
|
function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
|
|
8770
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
8771
|
+
return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
|
|
8772
|
+
}
|
|
8773
|
+
/**
|
|
8774
|
+
* Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
|
|
8775
|
+
* to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
|
|
8776
|
+
*
|
|
8777
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8778
|
+
*/
|
|
8779
|
+
function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
|
|
8608
8780
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
8609
8781
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
8610
8782
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
8611
8783
|
const upperBlend = Math.pow(1 - verticalProgress, 1.2);
|
|
8612
8784
|
const lowerBlend = Math.pow(verticalProgress, 1.42);
|
|
8613
|
-
const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
|
|
8614
8785
|
const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
|
|
8615
8786
|
const horizontalScale = 1.02 +
|
|
8616
8787
|
skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
|
|
@@ -8648,7 +8819,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
|
|
|
8648
8819
|
*
|
|
8649
8820
|
* @private helper of `octopus3d2AvatarVisual`
|
|
8650
8821
|
*/
|
|
8651
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
8822
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
8652
8823
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
8653
8824
|
if (tonalProgress < 0.16) {
|
|
8654
8825
|
return palette.highlight;
|
|
@@ -8688,6 +8859,1384 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
|
|
|
8688
8859
|
context.restore();
|
|
8689
8860
|
}
|
|
8690
8861
|
|
|
8862
|
+
/* eslint-disable no-magic-numbers */
|
|
8863
|
+
/**
|
|
8864
|
+
* Light direction used by the continuous octopus mesh shading.
|
|
8865
|
+
*
|
|
8866
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8867
|
+
*/
|
|
8868
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
8869
|
+
x: 0.34,
|
|
8870
|
+
y: -0.62,
|
|
8871
|
+
z: 1,
|
|
8872
|
+
});
|
|
8873
|
+
/**
|
|
8874
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
8875
|
+
*
|
|
8876
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8877
|
+
*/
|
|
8878
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
8879
|
+
/**
|
|
8880
|
+
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
8881
|
+
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
8882
|
+
* React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
|
|
8883
|
+
*
|
|
8884
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8885
|
+
*/
|
|
8886
|
+
const stableStateCache = new WeakMap();
|
|
8887
|
+
/**
|
|
8888
|
+
* Returns the stable per-avatar state, computing it on first access and returning the cached
|
|
8889
|
+
* result on every subsequent call within the same `<Avatar/>` mount.
|
|
8890
|
+
*
|
|
8891
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8892
|
+
*/
|
|
8893
|
+
function getOctopus3d3StableState(createRandom) {
|
|
8894
|
+
const cached = stableStateCache.get(createRandom);
|
|
8895
|
+
if (cached !== undefined) {
|
|
8896
|
+
return cached;
|
|
8897
|
+
}
|
|
8898
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
8899
|
+
const animationRandom = createRandom('octopus3d3-animation-profile');
|
|
8900
|
+
const eyeRandom = createRandom('octopus3d3-eye-profile');
|
|
8901
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
8902
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
8903
|
+
const state = {
|
|
8904
|
+
morphologyProfile,
|
|
8905
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
8906
|
+
leftEyePhaseOffset,
|
|
8907
|
+
rightEyePhaseOffset,
|
|
8908
|
+
tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
8909
|
+
};
|
|
8910
|
+
stableStateCache.set(createRandom, state);
|
|
8911
|
+
return state;
|
|
8912
|
+
}
|
|
8913
|
+
/**
|
|
8914
|
+
* Octopus 3D 3 avatar visual.
|
|
8915
|
+
*
|
|
8916
|
+
* @private built-in avatar visual
|
|
8917
|
+
*/
|
|
8918
|
+
const octopus3d3AvatarVisual = {
|
|
8919
|
+
id: 'octopus3d3',
|
|
8920
|
+
title: 'Octopus 3D 3',
|
|
8921
|
+
description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
|
|
8922
|
+
isAnimated: true,
|
|
8923
|
+
supportsPointerTracking: true,
|
|
8924
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
8925
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
|
|
8926
|
+
const sceneCenterX = size * 0.5;
|
|
8927
|
+
const sceneCenterY = size * 0.535;
|
|
8928
|
+
const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
|
|
8929
|
+
const meshCenter = {
|
|
8930
|
+
x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
8931
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
8932
|
+
z: interaction.intensity * size * 0.018,
|
|
8933
|
+
};
|
|
8934
|
+
const rotationY = -0.1 +
|
|
8935
|
+
Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
|
|
8936
|
+
interaction.bodyOffsetX * 0.22 +
|
|
8937
|
+
interaction.gazeX * 0.88;
|
|
8938
|
+
const rotationX = -0.07 +
|
|
8939
|
+
Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
|
|
8940
|
+
interaction.bodyOffsetY * 0.08 -
|
|
8941
|
+
interaction.gazeY * 0.38;
|
|
8942
|
+
const surfaceOptions = {
|
|
8943
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
|
|
8944
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
|
|
8945
|
+
radiusZ: size *
|
|
8946
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
8947
|
+
(1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
|
|
8948
|
+
morphologyProfile,
|
|
8949
|
+
timeMs,
|
|
8950
|
+
animationPhase,
|
|
8951
|
+
tentacleProfiles,
|
|
8952
|
+
};
|
|
8953
|
+
const surfacePatches = resolveVisibleContinuousOctopusPatches({
|
|
8954
|
+
...surfaceOptions,
|
|
8955
|
+
center: meshCenter,
|
|
8956
|
+
rotationX,
|
|
8957
|
+
rotationY,
|
|
8958
|
+
sceneCenterX,
|
|
8959
|
+
sceneCenterY,
|
|
8960
|
+
size,
|
|
8961
|
+
palette,
|
|
8962
|
+
});
|
|
8963
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
|
|
8964
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
|
|
8965
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
8966
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
8967
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
8968
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
8969
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
|
|
8970
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
|
|
8971
|
+
drawAvatarFrame(context, size, palette);
|
|
8972
|
+
drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
8973
|
+
drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
8974
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
8975
|
+
drawContinuousSurfacePatch(context, surfacePatch);
|
|
8976
|
+
}
|
|
8977
|
+
drawProjectedSurfaceCurrents({
|
|
8978
|
+
context,
|
|
8979
|
+
surfaceOptions,
|
|
8980
|
+
center: meshCenter,
|
|
8981
|
+
rotationX,
|
|
8982
|
+
rotationY,
|
|
8983
|
+
sceneCenterX,
|
|
8984
|
+
sceneCenterY,
|
|
8985
|
+
size,
|
|
8986
|
+
palette,
|
|
8987
|
+
morphologyProfile,
|
|
8988
|
+
timeMs,
|
|
8989
|
+
animationPhase,
|
|
8990
|
+
});
|
|
8991
|
+
drawProjectedTentacleSuckers({
|
|
8992
|
+
context,
|
|
8993
|
+
surfaceOptions,
|
|
8994
|
+
center: meshCenter,
|
|
8995
|
+
rotationX,
|
|
8996
|
+
rotationY,
|
|
8997
|
+
sceneCenterX,
|
|
8998
|
+
sceneCenterY,
|
|
8999
|
+
size,
|
|
9000
|
+
palette,
|
|
9001
|
+
});
|
|
9002
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9003
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9004
|
+
drawProjectedOrganicMouth(context, [
|
|
9005
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
9006
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
9007
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
9008
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
9009
|
+
},
|
|
9010
|
+
};
|
|
9011
|
+
/**
|
|
9012
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
9013
|
+
*
|
|
9014
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9015
|
+
*/
|
|
9016
|
+
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
9017
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
9018
|
+
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
9019
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
9020
|
+
return {
|
|
9021
|
+
centerLongitude: -Math.PI * 0.86 +
|
|
9022
|
+
progress * Math.PI * 1.72 +
|
|
9023
|
+
(tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
|
|
9024
|
+
widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
|
|
9025
|
+
lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
|
|
9026
|
+
swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
|
|
9027
|
+
depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
|
|
9028
|
+
phase: tentacleRandom() * Math.PI * 2,
|
|
9029
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
9030
|
+
};
|
|
9031
|
+
});
|
|
9032
|
+
}
|
|
9033
|
+
/**
|
|
9034
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
9035
|
+
*
|
|
9036
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9037
|
+
*/
|
|
9038
|
+
function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
9039
|
+
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.11, sceneCenterY - size * 0.17 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY, size * (0.66 + interaction.intensity * 0.02));
|
|
9040
|
+
glowGradient.addColorStop(0, `${palette.highlight}66`);
|
|
9041
|
+
glowGradient.addColorStop(0.34, `${palette.accent}2e`);
|
|
9042
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
9043
|
+
context.fillStyle = glowGradient;
|
|
9044
|
+
context.fillRect(0, 0, size, size);
|
|
9045
|
+
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1550) * size * 0.05, sceneCenterY + size * 0.29, size * 0.06, sceneCenterX, sceneCenterY + size * 0.3, size * 0.54);
|
|
9046
|
+
lowerGradient.addColorStop(0, `${palette.secondary}25`);
|
|
9047
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
9048
|
+
context.fillStyle = lowerGradient;
|
|
9049
|
+
context.fillRect(0, 0, size, size);
|
|
9050
|
+
}
|
|
9051
|
+
/**
|
|
9052
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
9053
|
+
*
|
|
9054
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
9055
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
9056
|
+
*
|
|
9057
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9058
|
+
*/
|
|
9059
|
+
function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
9060
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
9061
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
9062
|
+
const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
|
|
9063
|
+
const ry = size * 0.06;
|
|
9064
|
+
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
9065
|
+
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
9066
|
+
context.save();
|
|
9067
|
+
context.translate(cx, cy);
|
|
9068
|
+
context.scale(1, ry / rx);
|
|
9069
|
+
const blurRadius = rx * 1.4;
|
|
9070
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
9071
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
9072
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
9073
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
9074
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
9075
|
+
context.fillStyle = shadowGradient;
|
|
9076
|
+
context.beginPath();
|
|
9077
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
9078
|
+
context.fill();
|
|
9079
|
+
context.restore();
|
|
9080
|
+
}
|
|
9081
|
+
/**
|
|
9082
|
+
* Number of latitude segments used by the continuous Octopus 3D 3 mesh.
|
|
9083
|
+
*
|
|
9084
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9085
|
+
*/
|
|
9086
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
9087
|
+
/**
|
|
9088
|
+
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
9089
|
+
*
|
|
9090
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9091
|
+
*/
|
|
9092
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
9093
|
+
/**
|
|
9094
|
+
* Resolves visible projected patches for the continuous octopus mesh.
|
|
9095
|
+
*
|
|
9096
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
9097
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
9098
|
+
* than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
|
|
9099
|
+
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
9100
|
+
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
9101
|
+
*
|
|
9102
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9103
|
+
*/
|
|
9104
|
+
function resolveVisibleContinuousOctopusPatches(options) {
|
|
9105
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
9106
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
9107
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
9108
|
+
const surfacePatches = [];
|
|
9109
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
9110
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
9111
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
9112
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
9113
|
+
}
|
|
9114
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9115
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
9116
|
+
}
|
|
9117
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
9118
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
9119
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
9120
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9121
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9122
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9123
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
9124
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
|
|
9125
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
|
|
9126
|
+
}
|
|
9127
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9128
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
9129
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
|
|
9130
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
|
|
9131
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
9132
|
+
}
|
|
9133
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
9134
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
9135
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
9136
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
9137
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
9138
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
9139
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
9140
|
+
const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
9141
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
9145
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
9146
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
9147
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
9148
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
9149
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
9150
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
9151
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9152
|
+
const transformedCorners = [
|
|
9153
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
9154
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
9155
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
9156
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
9157
|
+
];
|
|
9158
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
9159
|
+
if (surfaceNormal.z <= 0.008) {
|
|
9160
|
+
continue;
|
|
9161
|
+
}
|
|
9162
|
+
const projectedCorners = [
|
|
9163
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
9164
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
9165
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
9166
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
9167
|
+
];
|
|
9168
|
+
surfacePatches.push({
|
|
9169
|
+
corners: projectedCorners,
|
|
9170
|
+
averageDepth: (transformedCorners[0].z +
|
|
9171
|
+
transformedCorners[1].z +
|
|
9172
|
+
transformedCorners[2].z +
|
|
9173
|
+
transformedCorners[3].z) /
|
|
9174
|
+
4,
|
|
9175
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
9176
|
+
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
9177
|
+
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
9178
|
+
});
|
|
9179
|
+
}
|
|
9180
|
+
}
|
|
9181
|
+
return surfacePatches;
|
|
9182
|
+
}
|
|
9183
|
+
/**
|
|
9184
|
+
* Samples one point on the continuous Octopus 3D 3 surface.
|
|
9185
|
+
*
|
|
9186
|
+
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
9187
|
+
* tentacled while still being rendered as one connected blobby mesh.
|
|
9188
|
+
*
|
|
9189
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9190
|
+
*/
|
|
9191
|
+
function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
|
|
9192
|
+
return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
|
|
9193
|
+
}
|
|
9194
|
+
/**
|
|
9195
|
+
* Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
|
|
9196
|
+
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
9197
|
+
*
|
|
9198
|
+
* The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
|
|
9199
|
+
* corner grid, so the same longitude is reused across every latitude row and each
|
|
9200
|
+
* tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
|
|
9201
|
+
*
|
|
9202
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9203
|
+
*/
|
|
9204
|
+
function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
9205
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
9206
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
9207
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
9208
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
9209
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
9210
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
9211
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
9212
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
9213
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
9214
|
+
animationPhase * 0.6 +
|
|
9215
|
+
timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
|
|
9216
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
|
|
9217
|
+
(0.3 + lowerBlend * 0.7);
|
|
9218
|
+
const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
|
|
9219
|
+
lowerBlend *
|
|
9220
|
+
tentacleInfluence.core *
|
|
9221
|
+
tentacleInfluence.swayScale;
|
|
9222
|
+
const horizontalScale = 1.04 +
|
|
9223
|
+
mantleRipple +
|
|
9224
|
+
lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
|
|
9225
|
+
lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
|
|
9226
|
+
upperBlend * 0.08;
|
|
9227
|
+
const depthScale = 1.06 +
|
|
9228
|
+
upperBlend * 0.16 +
|
|
9229
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
|
|
9230
|
+
lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
|
|
9231
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
9232
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
|
|
9233
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
9234
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
|
|
9235
|
+
const lowerDrop = lowerBlend *
|
|
9236
|
+
radiusY *
|
|
9237
|
+
(0.18 +
|
|
9238
|
+
tentacleInfluence.core *
|
|
9239
|
+
(0.38 +
|
|
9240
|
+
tentacleInfluence.lengthScale * 0.22 +
|
|
9241
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
|
|
9242
|
+
return {
|
|
9243
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
|
|
9244
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
|
|
9245
|
+
upperBlend * radiusY * 0.1 +
|
|
9246
|
+
lowerDrop +
|
|
9247
|
+
Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
|
|
9248
|
+
Math.cos(timeMs / 880 + tentacleInfluence.phase) *
|
|
9249
|
+
lowerBlend *
|
|
9250
|
+
tipBlend *
|
|
9251
|
+
tentacleInfluence.core *
|
|
9252
|
+
radiusY *
|
|
9253
|
+
0.034,
|
|
9254
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
9255
|
+
Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
|
|
9256
|
+
lowerBlend *
|
|
9257
|
+
tentacleInfluence.core *
|
|
9258
|
+
radiusZ *
|
|
9259
|
+
0.04,
|
|
9260
|
+
};
|
|
9261
|
+
}
|
|
9262
|
+
/**
|
|
9263
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
9264
|
+
*
|
|
9265
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9266
|
+
*/
|
|
9267
|
+
function resolveContinuousTentacleInfluence(options, longitude) {
|
|
9268
|
+
let totalWeight = 0;
|
|
9269
|
+
let weightedSin = 0;
|
|
9270
|
+
let weightedCos = 0;
|
|
9271
|
+
let weightedWidthScale = 0;
|
|
9272
|
+
let weightedLengthScale = 0;
|
|
9273
|
+
let weightedSwayScale = 0;
|
|
9274
|
+
let weightedDepthScale = 0;
|
|
9275
|
+
let weightedPhase = 0;
|
|
9276
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
9277
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
9278
|
+
const width = 0.2 * tentacleProfile.widthScale;
|
|
9279
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
9280
|
+
totalWeight += weight;
|
|
9281
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
9282
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
9283
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
9284
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
9285
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
9286
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
9287
|
+
weightedPhase += tentacleProfile.phase * weight;
|
|
9288
|
+
}
|
|
9289
|
+
if (totalWeight < 0.0001) {
|
|
9290
|
+
return {
|
|
9291
|
+
core: 0,
|
|
9292
|
+
centerLongitude: longitude,
|
|
9293
|
+
widthScale: 1,
|
|
9294
|
+
lengthScale: 1,
|
|
9295
|
+
swayScale: 1,
|
|
9296
|
+
depthScale: 1,
|
|
9297
|
+
phase: 0,
|
|
9298
|
+
};
|
|
9299
|
+
}
|
|
9300
|
+
return {
|
|
9301
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
9302
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
9303
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
9304
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
9305
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
9306
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
9307
|
+
phase: weightedPhase / totalWeight,
|
|
9308
|
+
};
|
|
9309
|
+
}
|
|
9310
|
+
/**
|
|
9311
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
9312
|
+
*
|
|
9313
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9314
|
+
*/
|
|
9315
|
+
function resolveContinuousLobeWave(options, longitude) {
|
|
9316
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
9317
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
9318
|
+
animationPhase +
|
|
9319
|
+
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
9320
|
+
1) /
|
|
9321
|
+
2);
|
|
9322
|
+
}
|
|
9323
|
+
/**
|
|
9324
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
9325
|
+
*
|
|
9326
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9327
|
+
*/
|
|
9328
|
+
function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
9329
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
9330
|
+
if (tonalProgress < 0.14) {
|
|
9331
|
+
return palette.highlight;
|
|
9332
|
+
}
|
|
9333
|
+
if (tonalProgress < 0.32) {
|
|
9334
|
+
return palette.secondary;
|
|
9335
|
+
}
|
|
9336
|
+
if (tonalProgress < 0.72) {
|
|
9337
|
+
return forwardness > 0.55 ? palette.secondary : palette.primary;
|
|
9338
|
+
}
|
|
9339
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
9340
|
+
}
|
|
9341
|
+
/**
|
|
9342
|
+
* Draws one projected mesh patch with soft shading and a subtle edge.
|
|
9343
|
+
*
|
|
9344
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9345
|
+
*/
|
|
9346
|
+
function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
9347
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
9348
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
9349
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
|
|
9350
|
+
}
|
|
9351
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
9352
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
9353
|
+
}
|
|
9354
|
+
context.save();
|
|
9355
|
+
context.beginPath();
|
|
9356
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
9357
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
9358
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
9359
|
+
}
|
|
9360
|
+
context.closePath();
|
|
9361
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
9362
|
+
context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
|
|
9363
|
+
context.lineJoin = 'round';
|
|
9364
|
+
context.stroke();
|
|
9365
|
+
context.restore();
|
|
9366
|
+
}
|
|
9367
|
+
/**
|
|
9368
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
9369
|
+
*
|
|
9370
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9371
|
+
*/
|
|
9372
|
+
function drawProjectedSurfaceCurrents(options) {
|
|
9373
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
9374
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
9375
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
9376
|
+
context.save();
|
|
9377
|
+
context.lineCap = 'round';
|
|
9378
|
+
context.lineJoin = 'round';
|
|
9379
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
9380
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
9381
|
+
const projectedPoints = [];
|
|
9382
|
+
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
|
|
9383
|
+
const progress = sampleIndex / 7;
|
|
9384
|
+
const latitude = -0.46 + progress * 0.74;
|
|
9385
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
|
|
9386
|
+
const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
9387
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
9388
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
9389
|
+
}
|
|
9390
|
+
}
|
|
9391
|
+
if (projectedPoints.length < 3) {
|
|
9392
|
+
continue;
|
|
9393
|
+
}
|
|
9394
|
+
context.beginPath();
|
|
9395
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
9396
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
9397
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
9398
|
+
}
|
|
9399
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
9400
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
9401
|
+
context.stroke();
|
|
9402
|
+
}
|
|
9403
|
+
context.restore();
|
|
9404
|
+
}
|
|
9405
|
+
/**
|
|
9406
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
9407
|
+
*
|
|
9408
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9409
|
+
*/
|
|
9410
|
+
function drawProjectedTentacleSuckers(options) {
|
|
9411
|
+
const { surfaceOptions, size } = options;
|
|
9412
|
+
const { timeMs } = surfaceOptions;
|
|
9413
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
9414
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
|
|
9415
|
+
continue;
|
|
9416
|
+
}
|
|
9417
|
+
for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
|
|
9418
|
+
const latitude = 0.52 + suckerIndex * 0.14;
|
|
9419
|
+
const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
|
|
9420
|
+
const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
|
|
9421
|
+
drawProjectedSurfaceSpot({
|
|
9422
|
+
...options,
|
|
9423
|
+
latitude,
|
|
9424
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
9425
|
+
radiusScale: size * (0.0065 - suckerIndex * 0.0007),
|
|
9426
|
+
});
|
|
9427
|
+
}
|
|
9428
|
+
}
|
|
9429
|
+
}
|
|
9430
|
+
/**
|
|
9431
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
9432
|
+
*
|
|
9433
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9434
|
+
*/
|
|
9435
|
+
function drawProjectedSurfaceSpot(options) {
|
|
9436
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
9437
|
+
const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
|
|
9438
|
+
const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
9439
|
+
const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
9440
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
9441
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
9442
|
+
return;
|
|
9443
|
+
}
|
|
9444
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
9445
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
9446
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
9447
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
9448
|
+
radiusScale *
|
|
9449
|
+
0.74, size * 0.003, size * 0.018);
|
|
9450
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
9451
|
+
radiusScale *
|
|
9452
|
+
0.52, size * 0.0024, size * 0.014);
|
|
9453
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
9454
|
+
context.save();
|
|
9455
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
9456
|
+
context.rotate(rotation);
|
|
9457
|
+
context.beginPath();
|
|
9458
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
9459
|
+
context.fillStyle = `${palette.highlight}73`;
|
|
9460
|
+
context.fill();
|
|
9461
|
+
context.strokeStyle = `${palette.highlight}99`;
|
|
9462
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
9463
|
+
context.stroke();
|
|
9464
|
+
context.restore();
|
|
9465
|
+
}
|
|
9466
|
+
/**
|
|
9467
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
9468
|
+
*
|
|
9469
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9470
|
+
*/
|
|
9471
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
9472
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
9473
|
+
}
|
|
9474
|
+
/**
|
|
9475
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
9476
|
+
*
|
|
9477
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9478
|
+
*/
|
|
9479
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
9480
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
9481
|
+
return progress * progress * (3 - 2 * progress);
|
|
9482
|
+
}
|
|
9483
|
+
|
|
9484
|
+
/* eslint-disable no-magic-numbers */
|
|
9485
|
+
/**
|
|
9486
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
9487
|
+
*
|
|
9488
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9489
|
+
*/
|
|
9490
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
9491
|
+
x: 0.32,
|
|
9492
|
+
y: -0.66,
|
|
9493
|
+
z: 1,
|
|
9494
|
+
});
|
|
9495
|
+
/**
|
|
9496
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
9497
|
+
*
|
|
9498
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9499
|
+
*/
|
|
9500
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
9501
|
+
x: -0.7,
|
|
9502
|
+
y: -0.18,
|
|
9503
|
+
z: 0.55,
|
|
9504
|
+
});
|
|
9505
|
+
/**
|
|
9506
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
9507
|
+
*
|
|
9508
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9509
|
+
*/
|
|
9510
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
9511
|
+
/**
|
|
9512
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
9513
|
+
*
|
|
9514
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9515
|
+
*/
|
|
9516
|
+
const SKIN_SPOT_COUNT = 14;
|
|
9517
|
+
/**
|
|
9518
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
9519
|
+
*
|
|
9520
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9521
|
+
*/
|
|
9522
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
9523
|
+
/**
|
|
9524
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
9525
|
+
*
|
|
9526
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9527
|
+
*/
|
|
9528
|
+
function getOctopus3d4StableState(createRandom) {
|
|
9529
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
9530
|
+
if (cached !== undefined) {
|
|
9531
|
+
return cached;
|
|
9532
|
+
}
|
|
9533
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
9534
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
9535
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
9536
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
9537
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
9538
|
+
const state = {
|
|
9539
|
+
morphologyProfile,
|
|
9540
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
9541
|
+
leftEyePhaseOffset,
|
|
9542
|
+
rightEyePhaseOffset,
|
|
9543
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
9544
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
9545
|
+
};
|
|
9546
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
9547
|
+
return state;
|
|
9548
|
+
}
|
|
9549
|
+
/**
|
|
9550
|
+
* Octopus 3D 4 avatar visual.
|
|
9551
|
+
*
|
|
9552
|
+
* @private built-in avatar visual
|
|
9553
|
+
*/
|
|
9554
|
+
const octopus3d4AvatarVisual = {
|
|
9555
|
+
id: 'octopus3d4',
|
|
9556
|
+
title: 'Octopus 3D 4',
|
|
9557
|
+
description: 'Cute blobby 3D octopus rendered as one smooth continuous mesh with naturally undulating tentacles, rich multi-pass shading, pigment spots, and cursor-aware head and eyes.',
|
|
9558
|
+
isAnimated: true,
|
|
9559
|
+
supportsPointerTracking: true,
|
|
9560
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
9561
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
9562
|
+
const sceneCenterX = size * 0.5;
|
|
9563
|
+
const sceneCenterY = size * 0.535;
|
|
9564
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
9565
|
+
const meshCenter = {
|
|
9566
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
9567
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
9568
|
+
z: interaction.intensity * size * 0.02,
|
|
9569
|
+
};
|
|
9570
|
+
const rotationY = -0.08 +
|
|
9571
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
9572
|
+
interaction.bodyOffsetX * 0.24 +
|
|
9573
|
+
interaction.gazeX * 0.98;
|
|
9574
|
+
const rotationX = -0.07 +
|
|
9575
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
9576
|
+
interaction.bodyOffsetY * 0.08 -
|
|
9577
|
+
interaction.gazeY * 0.42;
|
|
9578
|
+
const surfaceOptions = {
|
|
9579
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
9580
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
9581
|
+
radiusZ: size *
|
|
9582
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
9583
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
9584
|
+
morphologyProfile,
|
|
9585
|
+
timeMs,
|
|
9586
|
+
animationPhase,
|
|
9587
|
+
tentacleProfiles,
|
|
9588
|
+
};
|
|
9589
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
9590
|
+
...surfaceOptions,
|
|
9591
|
+
center: meshCenter,
|
|
9592
|
+
rotationX,
|
|
9593
|
+
rotationY,
|
|
9594
|
+
sceneCenterX,
|
|
9595
|
+
sceneCenterY,
|
|
9596
|
+
size,
|
|
9597
|
+
palette,
|
|
9598
|
+
});
|
|
9599
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
9600
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
9601
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
9602
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
9603
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
9604
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
9605
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
9606
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
9607
|
+
drawAvatarFrame(context, size, palette);
|
|
9608
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
9609
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
9610
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
9611
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
9612
|
+
}
|
|
9613
|
+
drawBlobbySkinSpots({
|
|
9614
|
+
context,
|
|
9615
|
+
surfaceOptions,
|
|
9616
|
+
center: meshCenter,
|
|
9617
|
+
rotationX,
|
|
9618
|
+
rotationY,
|
|
9619
|
+
sceneCenterX,
|
|
9620
|
+
sceneCenterY,
|
|
9621
|
+
size,
|
|
9622
|
+
palette,
|
|
9623
|
+
skinSpots,
|
|
9624
|
+
});
|
|
9625
|
+
drawBlobbyContinuousCurrents({
|
|
9626
|
+
context,
|
|
9627
|
+
surfaceOptions,
|
|
9628
|
+
center: meshCenter,
|
|
9629
|
+
rotationX,
|
|
9630
|
+
rotationY,
|
|
9631
|
+
sceneCenterX,
|
|
9632
|
+
sceneCenterY,
|
|
9633
|
+
size,
|
|
9634
|
+
palette,
|
|
9635
|
+
morphologyProfile,
|
|
9636
|
+
timeMs,
|
|
9637
|
+
animationPhase,
|
|
9638
|
+
});
|
|
9639
|
+
drawBlobbyContinuousSuckers({
|
|
9640
|
+
context,
|
|
9641
|
+
surfaceOptions,
|
|
9642
|
+
center: meshCenter,
|
|
9643
|
+
rotationX,
|
|
9644
|
+
rotationY,
|
|
9645
|
+
sceneCenterX,
|
|
9646
|
+
sceneCenterY,
|
|
9647
|
+
size,
|
|
9648
|
+
palette,
|
|
9649
|
+
});
|
|
9650
|
+
drawBlobbyContinuousGloss({
|
|
9651
|
+
context,
|
|
9652
|
+
surfaceOptions,
|
|
9653
|
+
center: meshCenter,
|
|
9654
|
+
rotationX,
|
|
9655
|
+
rotationY,
|
|
9656
|
+
sceneCenterX,
|
|
9657
|
+
sceneCenterY,
|
|
9658
|
+
size,
|
|
9659
|
+
palette,
|
|
9660
|
+
});
|
|
9661
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9662
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9663
|
+
drawProjectedOrganicMouth(context, [
|
|
9664
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
9665
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
9666
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
9667
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
9668
|
+
},
|
|
9669
|
+
};
|
|
9670
|
+
/**
|
|
9671
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
9672
|
+
*
|
|
9673
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9674
|
+
*/
|
|
9675
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
9676
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
9677
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
9678
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
9679
|
+
return {
|
|
9680
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
9681
|
+
progress * Math.PI * 1.8 +
|
|
9682
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
9683
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
9684
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
9685
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
9686
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
9687
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
9688
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9689
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9690
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
9691
|
+
};
|
|
9692
|
+
});
|
|
9693
|
+
}
|
|
9694
|
+
/**
|
|
9695
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
9696
|
+
*
|
|
9697
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9698
|
+
*/
|
|
9699
|
+
function createBlobbySkinSpots(createRandom) {
|
|
9700
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
9701
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
9702
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
9703
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
9704
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
9705
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
9706
|
+
}));
|
|
9707
|
+
}
|
|
9708
|
+
/**
|
|
9709
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
9710
|
+
*
|
|
9711
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9712
|
+
*/
|
|
9713
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
9714
|
+
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.12, sceneCenterY - size * 0.18 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY, size * (0.68 + interaction.intensity * 0.025));
|
|
9715
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
9716
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
9717
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
9718
|
+
context.fillStyle = glowGradient;
|
|
9719
|
+
context.fillRect(0, 0, size, size);
|
|
9720
|
+
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1500) * size * 0.05, sceneCenterY + size * 0.3, size * 0.06, sceneCenterX, sceneCenterY + size * 0.3, size * 0.56);
|
|
9721
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
9722
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
9723
|
+
context.fillStyle = lowerGradient;
|
|
9724
|
+
context.fillRect(0, 0, size, size);
|
|
9725
|
+
}
|
|
9726
|
+
/**
|
|
9727
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
9728
|
+
*
|
|
9729
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9730
|
+
*/
|
|
9731
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
9732
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
9733
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
9734
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
9735
|
+
const ry = size * 0.062;
|
|
9736
|
+
context.save();
|
|
9737
|
+
context.translate(cx, cy);
|
|
9738
|
+
context.scale(1, ry / rx);
|
|
9739
|
+
const blurRadius = rx * 1.42;
|
|
9740
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
9741
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
9742
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
9743
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
9744
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
9745
|
+
context.fillStyle = shadowGradient;
|
|
9746
|
+
context.beginPath();
|
|
9747
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
9748
|
+
context.fill();
|
|
9749
|
+
context.restore();
|
|
9750
|
+
}
|
|
9751
|
+
/**
|
|
9752
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
9753
|
+
*
|
|
9754
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9755
|
+
*/
|
|
9756
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
9757
|
+
/**
|
|
9758
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
9759
|
+
*
|
|
9760
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9761
|
+
*/
|
|
9762
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
9763
|
+
/**
|
|
9764
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
9765
|
+
*
|
|
9766
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
9767
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
9768
|
+
* than re-evaluated for every patch corner.
|
|
9769
|
+
*
|
|
9770
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9771
|
+
*/
|
|
9772
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
9773
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
9774
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
9775
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
9776
|
+
const surfacePatches = [];
|
|
9777
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
9778
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
9779
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
9780
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
9781
|
+
}
|
|
9782
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9783
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
9784
|
+
}
|
|
9785
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
9786
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
9787
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
9788
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9789
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9790
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9791
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
9792
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
9793
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
9794
|
+
}
|
|
9795
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9796
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
9797
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
9798
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
9799
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
9800
|
+
}
|
|
9801
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
9802
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
9803
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
9804
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
9805
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
9806
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
9807
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
9808
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
9809
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
9810
|
+
}
|
|
9811
|
+
}
|
|
9812
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
9813
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
9814
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
9815
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
9816
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
9817
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
9818
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
9819
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9820
|
+
const transformedCorners = [
|
|
9821
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
9822
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
9823
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
9824
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
9825
|
+
];
|
|
9826
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
9827
|
+
if (surfaceNormal.z <= 0.006) {
|
|
9828
|
+
continue;
|
|
9829
|
+
}
|
|
9830
|
+
const projectedCorners = [
|
|
9831
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
9832
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
9833
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
9834
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
9835
|
+
];
|
|
9836
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
9837
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
9838
|
+
surfacePatches.push({
|
|
9839
|
+
corners: projectedCorners,
|
|
9840
|
+
averageDepth: (transformedCorners[0].z +
|
|
9841
|
+
transformedCorners[1].z +
|
|
9842
|
+
transformedCorners[2].z +
|
|
9843
|
+
transformedCorners[3].z) /
|
|
9844
|
+
4,
|
|
9845
|
+
lightIntensity,
|
|
9846
|
+
rimLightIntensity,
|
|
9847
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
9848
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
9849
|
+
});
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
return surfacePatches;
|
|
9853
|
+
}
|
|
9854
|
+
/**
|
|
9855
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
9856
|
+
*
|
|
9857
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9858
|
+
*/
|
|
9859
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
9860
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
9861
|
+
}
|
|
9862
|
+
/**
|
|
9863
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
9864
|
+
*
|
|
9865
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
9866
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
9867
|
+
* of `latitudePatchCount * 4` times.
|
|
9868
|
+
*
|
|
9869
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9870
|
+
*/
|
|
9871
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
9872
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
9873
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
9874
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
9875
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
9876
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
9877
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
9878
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
9879
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
9880
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
9881
|
+
animationPhase * 0.6 +
|
|
9882
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
9883
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
9884
|
+
(0.32 + lowerBlend * 0.7);
|
|
9885
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
9886
|
+
lowerBlend *
|
|
9887
|
+
tentacleInfluence.core *
|
|
9888
|
+
tentacleInfluence.swayScale;
|
|
9889
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
9890
|
+
lowerBlend *
|
|
9891
|
+
tentacleInfluence.core *
|
|
9892
|
+
tentacleInfluence.swayScale *
|
|
9893
|
+
0.42;
|
|
9894
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
9895
|
+
tipBlend *
|
|
9896
|
+
tentacleInfluence.core *
|
|
9897
|
+
tentacleInfluence.curlScale *
|
|
9898
|
+
0.36;
|
|
9899
|
+
const horizontalScale = 1.06 +
|
|
9900
|
+
mantleRipple +
|
|
9901
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
9902
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
9903
|
+
upperBlend * 0.08;
|
|
9904
|
+
const depthScale = 1.08 +
|
|
9905
|
+
upperBlend * 0.16 +
|
|
9906
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
9907
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
9908
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
9909
|
+
const tentacleTubeRadius = lowerBlend *
|
|
9910
|
+
tentacleInfluence.core *
|
|
9911
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
9912
|
+
radiusX;
|
|
9913
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
9914
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
9915
|
+
const lowerDrop = lowerBlend *
|
|
9916
|
+
radiusY *
|
|
9917
|
+
(0.2 +
|
|
9918
|
+
tentacleInfluence.core *
|
|
9919
|
+
(0.42 +
|
|
9920
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
9921
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
9922
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
9923
|
+
return {
|
|
9924
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
9925
|
+
combinedTentacleSway +
|
|
9926
|
+
tentacleCurl * radiusX * 0.18,
|
|
9927
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
9928
|
+
upperBlend * radiusY * 0.12 +
|
|
9929
|
+
lowerDrop +
|
|
9930
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
9931
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
9932
|
+
lowerBlend *
|
|
9933
|
+
tipBlend *
|
|
9934
|
+
tentacleInfluence.core *
|
|
9935
|
+
radiusY *
|
|
9936
|
+
0.04,
|
|
9937
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
9938
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
9939
|
+
lowerBlend *
|
|
9940
|
+
tentacleInfluence.core *
|
|
9941
|
+
radiusZ *
|
|
9942
|
+
0.044 +
|
|
9943
|
+
tentacleCurl * radiusZ * 0.14,
|
|
9944
|
+
};
|
|
9945
|
+
}
|
|
9946
|
+
/**
|
|
9947
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
9948
|
+
*
|
|
9949
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9950
|
+
*/
|
|
9951
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
9952
|
+
let totalWeight = 0;
|
|
9953
|
+
let weightedSin = 0;
|
|
9954
|
+
let weightedCos = 0;
|
|
9955
|
+
let weightedWidthScale = 0;
|
|
9956
|
+
let weightedLengthScale = 0;
|
|
9957
|
+
let weightedSwayScale = 0;
|
|
9958
|
+
let weightedDepthScale = 0;
|
|
9959
|
+
let weightedCurlScale = 0;
|
|
9960
|
+
let weightedPrimaryPhase = 0;
|
|
9961
|
+
let weightedSecondaryPhase = 0;
|
|
9962
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
9963
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
9964
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
9965
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
9966
|
+
totalWeight += weight;
|
|
9967
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
9968
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
9969
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
9970
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
9971
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
9972
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
9973
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
9974
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
9975
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
9976
|
+
}
|
|
9977
|
+
if (totalWeight < 0.0001) {
|
|
9978
|
+
return {
|
|
9979
|
+
core: 0,
|
|
9980
|
+
centerLongitude: longitude,
|
|
9981
|
+
widthScale: 1,
|
|
9982
|
+
lengthScale: 1,
|
|
9983
|
+
swayScale: 1,
|
|
9984
|
+
depthScale: 1,
|
|
9985
|
+
curlScale: 1,
|
|
9986
|
+
primaryPhase: 0,
|
|
9987
|
+
secondaryPhase: 0,
|
|
9988
|
+
};
|
|
9989
|
+
}
|
|
9990
|
+
return {
|
|
9991
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
9992
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
9993
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
9994
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
9995
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
9996
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
9997
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
9998
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
9999
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
10000
|
+
};
|
|
10001
|
+
}
|
|
10002
|
+
/**
|
|
10003
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
10004
|
+
*
|
|
10005
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10006
|
+
*/
|
|
10007
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
10008
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
10009
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
10010
|
+
animationPhase +
|
|
10011
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
10012
|
+
1) /
|
|
10013
|
+
2);
|
|
10014
|
+
}
|
|
10015
|
+
/**
|
|
10016
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
10017
|
+
*
|
|
10018
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10019
|
+
*/
|
|
10020
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
10021
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
10022
|
+
if (tonalProgress < 0.12) {
|
|
10023
|
+
return palette.highlight;
|
|
10024
|
+
}
|
|
10025
|
+
if (tonalProgress < 0.3) {
|
|
10026
|
+
return palette.secondary;
|
|
10027
|
+
}
|
|
10028
|
+
if (tonalProgress < 0.72) {
|
|
10029
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
10030
|
+
}
|
|
10031
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
10032
|
+
}
|
|
10033
|
+
/**
|
|
10034
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
10035
|
+
*
|
|
10036
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10037
|
+
*/
|
|
10038
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
10039
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
10040
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
10041
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
10042
|
+
}
|
|
10043
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
10044
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
10045
|
+
}
|
|
10046
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
10047
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
10048
|
+
}
|
|
10049
|
+
context.save();
|
|
10050
|
+
context.beginPath();
|
|
10051
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
10052
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
10053
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
10054
|
+
}
|
|
10055
|
+
context.closePath();
|
|
10056
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
10057
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
10058
|
+
context.lineJoin = 'round';
|
|
10059
|
+
context.stroke();
|
|
10060
|
+
context.restore();
|
|
10061
|
+
}
|
|
10062
|
+
/**
|
|
10063
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
10064
|
+
*
|
|
10065
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10066
|
+
*/
|
|
10067
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
10068
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
10069
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
10070
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
10071
|
+
context.save();
|
|
10072
|
+
context.lineCap = 'round';
|
|
10073
|
+
context.lineJoin = 'round';
|
|
10074
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
10075
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
10076
|
+
const projectedPoints = [];
|
|
10077
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
10078
|
+
const progress = sampleIndex / 8;
|
|
10079
|
+
const latitude = -0.48 + progress * 0.78;
|
|
10080
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
10081
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
10082
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
10083
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
10084
|
+
}
|
|
10085
|
+
}
|
|
10086
|
+
if (projectedPoints.length < 3) {
|
|
10087
|
+
continue;
|
|
10088
|
+
}
|
|
10089
|
+
context.beginPath();
|
|
10090
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
10091
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
10092
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
10093
|
+
}
|
|
10094
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
10095
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
10096
|
+
context.stroke();
|
|
10097
|
+
}
|
|
10098
|
+
context.restore();
|
|
10099
|
+
}
|
|
10100
|
+
/**
|
|
10101
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
10102
|
+
*
|
|
10103
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10104
|
+
*/
|
|
10105
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
10106
|
+
const { surfaceOptions, size } = options;
|
|
10107
|
+
const { timeMs } = surfaceOptions;
|
|
10108
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
10109
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
10110
|
+
continue;
|
|
10111
|
+
}
|
|
10112
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
10113
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
10114
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
10115
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
10116
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
10117
|
+
...options,
|
|
10118
|
+
latitude,
|
|
10119
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
10120
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
10121
|
+
});
|
|
10122
|
+
}
|
|
10123
|
+
}
|
|
10124
|
+
}
|
|
10125
|
+
/**
|
|
10126
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
10127
|
+
*
|
|
10128
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10129
|
+
*/
|
|
10130
|
+
function drawBlobbySkinSpots(options) {
|
|
10131
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
10132
|
+
for (const skinSpot of skinSpots) {
|
|
10133
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
10134
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10135
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
10136
|
+
continue;
|
|
10137
|
+
}
|
|
10138
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10139
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
10140
|
+
context.save();
|
|
10141
|
+
context.beginPath();
|
|
10142
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
10143
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
10144
|
+
context.fill();
|
|
10145
|
+
context.restore();
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
/**
|
|
10149
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
10150
|
+
*
|
|
10151
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10152
|
+
*/
|
|
10153
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
10154
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
10155
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
10156
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
10157
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
10158
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10159
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
10160
|
+
return;
|
|
10161
|
+
}
|
|
10162
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10163
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
10164
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
10165
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
10166
|
+
radiusScale *
|
|
10167
|
+
0.78, size * 0.003, size * 0.02);
|
|
10168
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
10169
|
+
radiusScale *
|
|
10170
|
+
0.54, size * 0.0024, size * 0.015);
|
|
10171
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
10172
|
+
context.save();
|
|
10173
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
10174
|
+
context.rotate(rotation);
|
|
10175
|
+
context.beginPath();
|
|
10176
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
10177
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
10178
|
+
context.fill();
|
|
10179
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
10180
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
10181
|
+
context.stroke();
|
|
10182
|
+
context.restore();
|
|
10183
|
+
}
|
|
10184
|
+
/**
|
|
10185
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
10186
|
+
*
|
|
10187
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10188
|
+
*/
|
|
10189
|
+
function drawBlobbyContinuousGloss(options) {
|
|
10190
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
10191
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
10192
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
10193
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
10194
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
10195
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10196
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
10197
|
+
return;
|
|
10198
|
+
}
|
|
10199
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10200
|
+
const glossRadius = size * 0.058;
|
|
10201
|
+
context.save();
|
|
10202
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
10203
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
10204
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
10205
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
10206
|
+
context.fillStyle = glossGradient;
|
|
10207
|
+
context.beginPath();
|
|
10208
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
10209
|
+
context.fill();
|
|
10210
|
+
context.restore();
|
|
10211
|
+
}
|
|
10212
|
+
/**
|
|
10213
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
10214
|
+
*
|
|
10215
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10216
|
+
*/
|
|
10217
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
10218
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
10219
|
+
}
|
|
10220
|
+
/**
|
|
10221
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
10222
|
+
*
|
|
10223
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10224
|
+
*/
|
|
10225
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
10226
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
10227
|
+
return progress * progress * (3 - 2 * progress);
|
|
10228
|
+
}
|
|
10229
|
+
/**
|
|
10230
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
10231
|
+
*
|
|
10232
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
10233
|
+
*/
|
|
10234
|
+
function formatAlphaHex(opacity) {
|
|
10235
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
10236
|
+
.toString(16)
|
|
10237
|
+
.padStart(2, '0');
|
|
10238
|
+
}
|
|
10239
|
+
|
|
8691
10240
|
/* eslint-disable no-magic-numbers */
|
|
8692
10241
|
/**
|
|
8693
10242
|
* Octopus avatar visual.
|
|
@@ -9458,6 +11007,8 @@ const AVATAR_VISUALS = [
|
|
|
9458
11007
|
octopus3AvatarVisual,
|
|
9459
11008
|
octopus3dAvatarVisual,
|
|
9460
11009
|
octopus3d2AvatarVisual,
|
|
11010
|
+
octopus3d3AvatarVisual,
|
|
11011
|
+
octopus3d4AvatarVisual,
|
|
9461
11012
|
asciiOctopusAvatarVisual,
|
|
9462
11013
|
minecraftAvatarVisual,
|
|
9463
11014
|
minecraft2AvatarVisual,
|
|
@@ -10137,6 +11688,70 @@ class MetaLinkCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
10137
11688
|
}
|
|
10138
11689
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
10139
11690
|
|
|
11691
|
+
/**
|
|
11692
|
+
* META VISIBILITY commitment definition.
|
|
11693
|
+
*
|
|
11694
|
+
* The `META VISIBILITY` commitment stores whether an agent is public, private, or unlisted.
|
|
11695
|
+
* Agents Server mirrors this value into the database for efficient filtering, but the book
|
|
11696
|
+
* commitment remains the editable source of truth.
|
|
11697
|
+
*
|
|
11698
|
+
* @private Metadata-only commitment used by Agents Server.
|
|
11699
|
+
*/
|
|
11700
|
+
class MetaVisibilityCommitmentDefinition extends BaseCommitmentDefinition {
|
|
11701
|
+
constructor() {
|
|
11702
|
+
super('META VISIBILITY');
|
|
11703
|
+
}
|
|
11704
|
+
/**
|
|
11705
|
+
* Short one-line description of META VISIBILITY.
|
|
11706
|
+
*/
|
|
11707
|
+
get description() {
|
|
11708
|
+
return 'Set whether the agent is private, unlisted, or public.';
|
|
11709
|
+
}
|
|
11710
|
+
/**
|
|
11711
|
+
* Icon for this commitment.
|
|
11712
|
+
*/
|
|
11713
|
+
get icon() {
|
|
11714
|
+
return '👁️';
|
|
11715
|
+
}
|
|
11716
|
+
/**
|
|
11717
|
+
* Markdown documentation for META VISIBILITY commitment.
|
|
11718
|
+
*/
|
|
11719
|
+
get documentation() {
|
|
11720
|
+
return spaceTrim$1(`
|
|
11721
|
+
# META VISIBILITY
|
|
11722
|
+
|
|
11723
|
+
Sets the agent visibility used by Agents Server.
|
|
11724
|
+
|
|
11725
|
+
## Allowed values
|
|
11726
|
+
|
|
11727
|
+
- \`PRIVATE\` - accessible only to signed-in users with access.
|
|
11728
|
+
- \`UNLISTED\` - accessible by direct link but hidden from public listings.
|
|
11729
|
+
- \`PUBLIC\` - visible in public listings and accessible by anyone.
|
|
11730
|
+
|
|
11731
|
+
## Key aspects
|
|
11732
|
+
|
|
11733
|
+
- Does not modify the agent's behavior, system message, or tools.
|
|
11734
|
+
- Whitespace and letter case are normalized when persisted.
|
|
11735
|
+
- If multiple \`META VISIBILITY\` commitments are present, persistence keeps one normalized value.
|
|
11736
|
+
- Agents Server mirrors the value into the database for filtering, but the book is the source of truth.
|
|
11737
|
+
|
|
11738
|
+
## Example
|
|
11739
|
+
|
|
11740
|
+
\`\`\`book
|
|
11741
|
+
Helpful Assistant
|
|
11742
|
+
|
|
11743
|
+
GOAL Be helpful and friendly.
|
|
11744
|
+
META VISIBILITY PUBLIC
|
|
11745
|
+
\`\`\`
|
|
11746
|
+
`);
|
|
11747
|
+
}
|
|
11748
|
+
applyToAgentModelRequirements(requirements, content) {
|
|
11749
|
+
// META VISIBILITY is metadata only and does not alter model requirements.
|
|
11750
|
+
return requirements;
|
|
11751
|
+
}
|
|
11752
|
+
}
|
|
11753
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
11754
|
+
|
|
10140
11755
|
/**
|
|
10141
11756
|
* META VOICE commitment definition
|
|
10142
11757
|
*
|
|
@@ -11820,7 +13435,15 @@ function buildTeammateMetadata(entry) {
|
|
|
11820
13435
|
* Builds the teammate request text, optionally including context.
|
|
11821
13436
|
*/
|
|
11822
13437
|
function buildTeammateRequest(message, context) {
|
|
11823
|
-
|
|
13438
|
+
if (!context) {
|
|
13439
|
+
return message;
|
|
13440
|
+
}
|
|
13441
|
+
return spaceTrim$1((block) => `
|
|
13442
|
+
${block(message)}
|
|
13443
|
+
|
|
13444
|
+
Context:
|
|
13445
|
+
${block(context)}
|
|
13446
|
+
`);
|
|
11824
13447
|
}
|
|
11825
13448
|
/**
|
|
11826
13449
|
* Builds a minimal chat prompt for teammate calls.
|
|
@@ -15843,7 +17466,11 @@ function createUseProjectToolFunctions() {
|
|
|
15843
17466
|
const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
|
|
15844
17467
|
const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
|
|
15845
17468
|
const contentToReturn = wasCharacterTruncated
|
|
15846
|
-
?
|
|
17469
|
+
? spaceTrim$1((block) => `
|
|
17470
|
+
${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
|
|
17471
|
+
|
|
17472
|
+
[...truncated...]
|
|
17473
|
+
`)
|
|
15847
17474
|
: lineRangedContent;
|
|
15848
17475
|
const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
|
|
15849
17476
|
if (wasCharacterTruncated) {
|
|
@@ -16620,31 +18247,65 @@ class UseSearchEngineCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
16620
18247
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
16621
18248
|
|
|
16622
18249
|
/**
|
|
16623
|
-
* This error
|
|
18250
|
+
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
|
|
16624
18251
|
*
|
|
16625
18252
|
* @public exported from `@promptbook/core`
|
|
16626
18253
|
*/
|
|
16627
|
-
class
|
|
18254
|
+
class ParseError extends Error {
|
|
16628
18255
|
constructor(message) {
|
|
16629
18256
|
super(message);
|
|
16630
|
-
this.name = '
|
|
16631
|
-
Object.setPrototypeOf(this,
|
|
18257
|
+
this.name = 'ParseError';
|
|
18258
|
+
Object.setPrototypeOf(this, ParseError.prototype);
|
|
16632
18259
|
}
|
|
16633
18260
|
}
|
|
18261
|
+
// TODO: Maybe split `ParseError` and `ApplyError`
|
|
16634
18262
|
|
|
16635
18263
|
/**
|
|
16636
|
-
*
|
|
18264
|
+
* Supported visibility states for persisted agents.
|
|
16637
18265
|
*
|
|
16638
18266
|
* @public exported from `@promptbook/core`
|
|
16639
18267
|
*/
|
|
16640
|
-
|
|
18268
|
+
const AGENT_VISIBILITY_VALUES = ['PRIVATE', 'UNLISTED', 'PUBLIC'];
|
|
18269
|
+
/**
|
|
18270
|
+
* Returns `true` when the value is one of supported visibility states.
|
|
18271
|
+
*
|
|
18272
|
+
* @param value - Raw value to validate.
|
|
18273
|
+
* @returns Whether the value is a valid `AgentVisibility`.
|
|
18274
|
+
*
|
|
18275
|
+
* @public exported from `@promptbook/core`
|
|
18276
|
+
*/
|
|
18277
|
+
function isAgentVisibility(value) {
|
|
18278
|
+
return typeof value === 'string' && AGENT_VISIBILITY_VALUES.includes(value);
|
|
18279
|
+
}
|
|
18280
|
+
/**
|
|
18281
|
+
* Normalizes raw visibility text into a supported value.
|
|
18282
|
+
*
|
|
18283
|
+
* @param value - Raw visibility value.
|
|
18284
|
+
* @returns Normalized visibility, or `null` when invalid.
|
|
18285
|
+
*
|
|
18286
|
+
* @public exported from `@promptbook/core`
|
|
18287
|
+
*/
|
|
18288
|
+
function normalizeAgentVisibility(value) {
|
|
18289
|
+
if (typeof value !== 'string') {
|
|
18290
|
+
return null;
|
|
18291
|
+
}
|
|
18292
|
+
const normalized = value.trim().toUpperCase();
|
|
18293
|
+
return isAgentVisibility(normalized) ? normalized : null;
|
|
18294
|
+
}
|
|
18295
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
18296
|
+
|
|
18297
|
+
/**
|
|
18298
|
+
* This error type indicates that some limit was reached
|
|
18299
|
+
*
|
|
18300
|
+
* @public exported from `@promptbook/core`
|
|
18301
|
+
*/
|
|
18302
|
+
class LimitReachedError extends Error {
|
|
16641
18303
|
constructor(message) {
|
|
16642
18304
|
super(message);
|
|
16643
|
-
this.name = '
|
|
16644
|
-
Object.setPrototypeOf(this,
|
|
18305
|
+
this.name = 'LimitReachedError';
|
|
18306
|
+
Object.setPrototypeOf(this, LimitReachedError.prototype);
|
|
16645
18307
|
}
|
|
16646
18308
|
}
|
|
16647
|
-
// TODO: Maybe split `ParseError` and `ApplyError`
|
|
16648
18309
|
|
|
16649
18310
|
/**
|
|
16650
18311
|
* Maximum allowed source length for create-agent payloads.
|
|
@@ -16953,7 +18614,7 @@ function getTimeoutToolRuntimeAdapterOrDisabledResult(action, runtimeContext) {
|
|
|
16953
18614
|
* @private internal helper function
|
|
16954
18615
|
*/
|
|
16955
18616
|
function $randomToken(randomness) {
|
|
16956
|
-
return
|
|
18617
|
+
return CryptoJS.lib.WordArray.random(randomness).toString(CryptoJS.enc.Hex);
|
|
16957
18618
|
}
|
|
16958
18619
|
// TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
|
|
16959
18620
|
// TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
@@ -18979,6 +20640,7 @@ const COMMITMENT_REGISTRY = [
|
|
|
18979
20640
|
new MetaDomainCommitmentDefinition(),
|
|
18980
20641
|
new MetaDisclaimerCommitmentDefinition(),
|
|
18981
20642
|
new MetaInputPlaceholderCommitmentDefinition(),
|
|
20643
|
+
new MetaVisibilityCommitmentDefinition(),
|
|
18982
20644
|
new MetaCommitmentDefinition(),
|
|
18983
20645
|
new MetaVoiceCommitmentDefinition(),
|
|
18984
20646
|
new NoteCommitmentDefinition('NOTE'),
|
|
@@ -20633,6 +22295,7 @@ const META_COMMITMENT_APPLIERS = {
|
|
|
20633
22295
|
'META COLOR': applyMetaColorContent,
|
|
20634
22296
|
'META FONT': applyMetaFontContent,
|
|
20635
22297
|
'META VOICE': applyMetaVoiceContent,
|
|
22298
|
+
'META VISIBILITY': applyMetaVisibilityContent,
|
|
20636
22299
|
};
|
|
20637
22300
|
/**
|
|
20638
22301
|
* Applies META-style commitments that mutate parsed profile metadata.
|
|
@@ -20662,6 +22325,10 @@ function applyGenericMetaCommitment(state, content) {
|
|
|
20662
22325
|
applyMetaAvatarContent(state, metaValue);
|
|
20663
22326
|
return;
|
|
20664
22327
|
}
|
|
22328
|
+
if (metaTypeRaw.toUpperCase() === 'VISIBILITY') {
|
|
22329
|
+
applyMetaVisibilityContent(state, metaValue);
|
|
22330
|
+
return;
|
|
22331
|
+
}
|
|
20665
22332
|
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
20666
22333
|
state.meta[metaType] = metaValue;
|
|
20667
22334
|
}
|
|
@@ -20738,6 +22405,15 @@ function applyMetaFontContent(state, content) {
|
|
|
20738
22405
|
function applyMetaVoiceContent(state, content) {
|
|
20739
22406
|
state.meta.voice = spaceTrim$1(content);
|
|
20740
22407
|
}
|
|
22408
|
+
/**
|
|
22409
|
+
* Applies META VISIBILITY content into the normalized `meta.visibility` field.
|
|
22410
|
+
*/
|
|
22411
|
+
function applyMetaVisibilityContent(state, content) {
|
|
22412
|
+
const visibility = normalizeAgentVisibility(content);
|
|
22413
|
+
if (visibility) {
|
|
22414
|
+
state.meta.visibility = visibility;
|
|
22415
|
+
}
|
|
22416
|
+
}
|
|
20741
22417
|
/**
|
|
20742
22418
|
* Normalizes the separator in the content
|
|
20743
22419
|
*
|
|
@@ -24847,7 +26523,7 @@ function $registeredScrapersMessage(availableScrapers) {
|
|
|
24847
26523
|
* @public exported from `@promptbook/editable`
|
|
24848
26524
|
*/
|
|
24849
26525
|
function knowledgeSourceContentToName(knowledgeSourceContent) {
|
|
24850
|
-
const hash = SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
|
|
26526
|
+
const hash = CryptoJS.SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
|
|
24851
26527
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
24852
26528
|
.toString( /* hex */)
|
|
24853
26529
|
.substring(0, 20);
|
|
@@ -25695,7 +27371,7 @@ function csvParse(value /* <- TODO: string_csv */, settings, schema /* <- TODO:
|
|
|
25695
27371
|
console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
|
|
25696
27372
|
value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
25697
27373
|
}
|
|
25698
|
-
const csv = parse(value, settings);
|
|
27374
|
+
const csv = papaparse.parse(value, settings);
|
|
25699
27375
|
return csv;
|
|
25700
27376
|
}
|
|
25701
27377
|
|
|
@@ -25780,10 +27456,10 @@ const CsvFormatParser = {
|
|
|
25780
27456
|
i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
|
|
25781
27457
|
);
|
|
25782
27458
|
*/
|
|
25783
|
-
await onProgress(unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
|
|
27459
|
+
await onProgress(papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
|
|
25784
27460
|
}
|
|
25785
27461
|
}
|
|
25786
|
-
return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
27462
|
+
return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
25787
27463
|
},
|
|
25788
27464
|
},
|
|
25789
27465
|
{
|
|
@@ -25811,7 +27487,7 @@ const CsvFormatParser = {
|
|
|
25811
27487
|
return /* not await */ mapCallback({ [key]: value }, index, array.length);
|
|
25812
27488
|
}));
|
|
25813
27489
|
}));
|
|
25814
|
-
return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
27490
|
+
return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
25815
27491
|
},
|
|
25816
27492
|
},
|
|
25817
27493
|
],
|
|
@@ -28092,11 +29768,19 @@ const JsonFileImportPlugin = {
|
|
|
28092
29768
|
try {
|
|
28093
29769
|
const json = JSON.parse(content);
|
|
28094
29770
|
const formattedJson = JSON.stringify(json, null, 4);
|
|
28095
|
-
return
|
|
29771
|
+
return spaceTrim$1((block) => `
|
|
29772
|
+
\`\`\`json
|
|
29773
|
+
${block(formattedJson)}
|
|
29774
|
+
\`\`\`
|
|
29775
|
+
`);
|
|
28096
29776
|
}
|
|
28097
29777
|
catch (error) {
|
|
28098
29778
|
// If JSON is invalid, still import it but maybe not as pretty JSON
|
|
28099
|
-
return
|
|
29779
|
+
return spaceTrim$1((block) => `
|
|
29780
|
+
\`\`\`json
|
|
29781
|
+
${block(content)}
|
|
29782
|
+
\`\`\`
|
|
29783
|
+
`);
|
|
28100
29784
|
}
|
|
28101
29785
|
},
|
|
28102
29786
|
};
|
|
@@ -28120,7 +29804,11 @@ const TextFileImportPlugin = {
|
|
|
28120
29804
|
import(content, mimeType) {
|
|
28121
29805
|
const extension = mimeTypeToExtension(mimeType);
|
|
28122
29806
|
const codeBlockType = extension || 'txt';
|
|
28123
|
-
return
|
|
29807
|
+
return spaceTrim$1((block) => `
|
|
29808
|
+
\`\`\`${codeBlockType}
|
|
29809
|
+
${block(content)}
|
|
29810
|
+
\`\`\`
|
|
29811
|
+
`);
|
|
28124
29812
|
},
|
|
28125
29813
|
};
|
|
28126
29814
|
|
|
@@ -28367,11 +30055,19 @@ function collectExampleInteractionLines(parseResult, samples) {
|
|
|
28367
30055
|
const examples = [];
|
|
28368
30056
|
const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
|
|
28369
30057
|
if (initialMessage) {
|
|
28370
|
-
examples.push(
|
|
30058
|
+
examples.push(spaceTrim$1((block) => `
|
|
30059
|
+
**Agent:**
|
|
30060
|
+
${block(initialMessage)}
|
|
30061
|
+
`));
|
|
28371
30062
|
}
|
|
28372
30063
|
if (samples && samples.length > 0) {
|
|
28373
30064
|
for (const sample of samples) {
|
|
28374
|
-
examples.push(
|
|
30065
|
+
examples.push(spaceTrim$1((block) => `
|
|
30066
|
+
**User:** ${block(String(sample.question))}
|
|
30067
|
+
|
|
30068
|
+
**Agent:**
|
|
30069
|
+
${block(sample.answer)}
|
|
30070
|
+
`));
|
|
28375
30071
|
}
|
|
28376
30072
|
}
|
|
28377
30073
|
return examples;
|
|
@@ -29706,7 +31402,9 @@ class OpenAiAgentKitExecutionToolsToolBuilder {
|
|
|
29706
31402
|
if (!executionTools || !executionTools.script) {
|
|
29707
31403
|
throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
|
|
29708
31404
|
}
|
|
29709
|
-
return Array.isArray(executionTools.script)
|
|
31405
|
+
return Array.isArray(executionTools.script)
|
|
31406
|
+
? executionTools.script
|
|
31407
|
+
: [executionTools.script];
|
|
29710
31408
|
}
|
|
29711
31409
|
/**
|
|
29712
31410
|
* Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
|
|
@@ -32768,7 +34466,7 @@ class OpenAiVectorStoreKnowledgeSourcePreparer {
|
|
|
32768
34466
|
return { skippedReason: 'invalid_data_url' };
|
|
32769
34467
|
}
|
|
32770
34468
|
return {
|
|
32771
|
-
file: new File([parsed.buffer], parsed.filename, {
|
|
34469
|
+
file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
|
|
32772
34470
|
type: parsed.mimeType,
|
|
32773
34471
|
}),
|
|
32774
34472
|
sizeBytes: parsed.buffer.length,
|
|
@@ -34516,7 +36214,7 @@ function emitAgentLlmExecutionToolsAssistantPreparationProgress(options) {
|
|
|
34516
36214
|
* Computes one stable hash from a JSON-serializable value.
|
|
34517
36215
|
*/
|
|
34518
36216
|
function computeJsonHash$1(value) {
|
|
34519
|
-
return SHA256(JSON.stringify(value)).toString();
|
|
36217
|
+
return CryptoJS.SHA256(JSON.stringify(value)).toString();
|
|
34520
36218
|
}
|
|
34521
36219
|
/**
|
|
34522
36220
|
* Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
|
|
@@ -34674,7 +36372,7 @@ AgentLlmExecutionToolsAgentKitRunner.vectorStoreCache = new Map();
|
|
|
34674
36372
|
* Computes one stable hash from a JSON-serializable value.
|
|
34675
36373
|
*/
|
|
34676
36374
|
function computeJsonHash(value) {
|
|
34677
|
-
return SHA256(JSON.stringify(value)).toString();
|
|
36375
|
+
return CryptoJS.SHA256(JSON.stringify(value)).toString();
|
|
34678
36376
|
}
|
|
34679
36377
|
/**
|
|
34680
36378
|
* Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
|
|
@@ -35064,7 +36762,11 @@ function truncateAttachmentInlineText(content, maxCharacters) {
|
|
|
35064
36762
|
};
|
|
35065
36763
|
}
|
|
35066
36764
|
return {
|
|
35067
|
-
content:
|
|
36765
|
+
content: spaceTrim$1((block) => `
|
|
36766
|
+
${block(content.slice(0, Math.max(0, maxCharacters)))}
|
|
36767
|
+
|
|
36768
|
+
[...truncated...]
|
|
36769
|
+
`),
|
|
35068
36770
|
isTruncated: true,
|
|
35069
36771
|
};
|
|
35070
36772
|
}
|
|
@@ -35530,7 +37232,7 @@ class AgentLlmExecutionTools {
|
|
|
35530
37232
|
* Returns a virtual model name representing the agent behavior.
|
|
35531
37233
|
*/
|
|
35532
37234
|
get modelName() {
|
|
35533
|
-
const hash = SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
|
|
37235
|
+
const hash = CryptoJS.SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
|
|
35534
37236
|
const agentId = hash.substring(0, 10);
|
|
35535
37237
|
return (normalizeToKebabCase(this.title) + '-' + agentId);
|
|
35536
37238
|
}
|