@promptbook/remote-server 0.112.0-98 → 0.112.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 +37 -21
- package/esm/index.es.js +1688 -98
- 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 +0 -2
- package/esm/src/_packages/node.index.d.ts +40 -0
- package/esm/src/_packages/types.index.d.ts +18 -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-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 +1 -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 +17 -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/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -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 +1689 -96
- 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 +0 -2
- package/umd/src/_packages/node.index.d.ts +40 -0
- package/umd/src/_packages/types.index.d.ts +18 -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-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 +1 -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 +17 -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/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -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
|
@@ -9,19 +9,19 @@ import { Server } from 'socket.io';
|
|
|
9
9
|
import { stat, access, constants, readFile, writeFile, readdir, mkdir, watch, rm } from 'fs/promises';
|
|
10
10
|
import { Readability } from '@mozilla/readability';
|
|
11
11
|
import { JSDOM } from 'jsdom';
|
|
12
|
-
import
|
|
12
|
+
import CryptoJS from 'crypto-js';
|
|
13
13
|
import hexEncoder from 'crypto-js/enc-hex';
|
|
14
|
-
import
|
|
14
|
+
import showdown from 'showdown';
|
|
15
15
|
import * as OpenApiValidator from 'express-openapi-validator';
|
|
16
16
|
import swaggerUi from 'swagger-ui-express';
|
|
17
17
|
import { createElement } from 'react';
|
|
18
18
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
19
19
|
import { Subject, BehaviorSubject } from 'rxjs';
|
|
20
|
-
import
|
|
20
|
+
import 'crypto';
|
|
21
21
|
import moment from 'moment';
|
|
22
22
|
import { lookup, extension } from 'mime-types';
|
|
23
23
|
import sha256 from 'crypto-js/sha256';
|
|
24
|
-
import
|
|
24
|
+
import papaparse from 'papaparse';
|
|
25
25
|
import { fileSearchTool, tool, Agent as Agent$1, webSearchTool, run, setDefaultOpenAIClient, setDefaultOpenAIKey } from '@openai/agents';
|
|
26
26
|
import Bottleneck from 'bottleneck';
|
|
27
27
|
import OpenAI from 'openai';
|
|
@@ -40,7 +40,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
40
40
|
* @generated
|
|
41
41
|
* @see https://github.com/webgptorg/promptbook
|
|
42
42
|
*/
|
|
43
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0
|
|
43
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0';
|
|
44
44
|
/**
|
|
45
45
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
46
46
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1948,7 +1948,7 @@ class ParseError extends Error {
|
|
|
1948
1948
|
* @private internal helper function
|
|
1949
1949
|
*/
|
|
1950
1950
|
function $randomToken(randomness) {
|
|
1951
|
-
return
|
|
1951
|
+
return CryptoJS.lib.WordArray.random(randomness).toString(CryptoJS.enc.Hex);
|
|
1952
1952
|
}
|
|
1953
1953
|
// TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
|
|
1954
1954
|
// TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
@@ -5492,7 +5492,7 @@ function normalizeToKebabCase(text) {
|
|
|
5492
5492
|
* @public exported from `@promptbook/editable`
|
|
5493
5493
|
*/
|
|
5494
5494
|
function knowledgeSourceContentToName(knowledgeSourceContent) {
|
|
5495
|
-
const hash = SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
|
|
5495
|
+
const hash = CryptoJS.SHA256(hexEncoder.parse(JSON.stringify(knowledgeSourceContent)))
|
|
5496
5496
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
5497
5497
|
.toString( /* hex */)
|
|
5498
5498
|
.substring(0, 20);
|
|
@@ -6486,7 +6486,7 @@ function csvParse(value /* <- TODO: string_csv */, settings, schema /* <- TODO:
|
|
|
6486
6486
|
console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
|
|
6487
6487
|
value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
6488
6488
|
}
|
|
6489
|
-
const csv = parse(value, settings);
|
|
6489
|
+
const csv = papaparse.parse(value, settings);
|
|
6490
6490
|
return csv;
|
|
6491
6491
|
}
|
|
6492
6492
|
|
|
@@ -6571,10 +6571,10 @@ const CsvFormatParser = {
|
|
|
6571
6571
|
i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
|
|
6572
6572
|
);
|
|
6573
6573
|
*/
|
|
6574
|
-
await onProgress(unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
|
|
6574
|
+
await onProgress(papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
|
|
6575
6575
|
}
|
|
6576
6576
|
}
|
|
6577
|
-
return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6577
|
+
return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6578
6578
|
},
|
|
6579
6579
|
},
|
|
6580
6580
|
{
|
|
@@ -6602,7 +6602,7 @@ const CsvFormatParser = {
|
|
|
6602
6602
|
return /* not await */ mapCallback({ [key]: value }, index, array.length);
|
|
6603
6603
|
}));
|
|
6604
6604
|
}));
|
|
6605
|
-
return unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6605
|
+
return papaparse.unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6606
6606
|
},
|
|
6607
6607
|
},
|
|
6608
6608
|
],
|
|
@@ -9259,7 +9259,8 @@ function colorizeAvailableProviderMessage(providerMessage, { isInstalled, isFull
|
|
|
9259
9259
|
* @private internal function of `$registeredLlmToolsMessage`
|
|
9260
9260
|
*/
|
|
9261
9261
|
function createUsedEnvMessage() {
|
|
9262
|
-
return `Unknown \`.env\` file`
|
|
9262
|
+
return `Unknown \`.env\` file`
|
|
9263
|
+
;
|
|
9263
9264
|
}
|
|
9264
9265
|
// TODO: [®] DRY Register logic
|
|
9265
9266
|
// TODO: [🧠][⚛] Maybe pass env as argument
|
|
@@ -9531,7 +9532,7 @@ async function getScraperIntermediateSource(source, options) {
|
|
|
9531
9532
|
const { filename: sourceFilename, url } = source;
|
|
9532
9533
|
const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
|
|
9533
9534
|
// TODO: [👬] DRY
|
|
9534
|
-
const hash = SHA256(
|
|
9535
|
+
const hash = CryptoJS.SHA256(
|
|
9535
9536
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
9536
9537
|
hexEncoder.parse(sourceFilename || url || 'untitled'))
|
|
9537
9538
|
.toString( /* hex */)
|
|
@@ -9622,7 +9623,7 @@ $scrapersMetadataRegister.register(websiteScraperMetadata);
|
|
|
9622
9623
|
* @private utility of `WebsiteScraper`
|
|
9623
9624
|
*/
|
|
9624
9625
|
function createShowdownConverter() {
|
|
9625
|
-
return new Converter({
|
|
9626
|
+
return new showdown.Converter({
|
|
9626
9627
|
flavor: 'github',
|
|
9627
9628
|
/*
|
|
9628
9629
|
> import showdownHighlight from 'showdown-highlight';
|
|
@@ -10814,7 +10815,7 @@ function normalizeWhitespaces(sentence) {
|
|
|
10814
10815
|
* @public exported from `@promptbook/utils`
|
|
10815
10816
|
*/
|
|
10816
10817
|
function computeHash(value) {
|
|
10817
|
-
return SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
|
|
10818
|
+
return CryptoJS.SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
|
|
10818
10819
|
}
|
|
10819
10820
|
// TODO: [🥬][🥬] Use this ACRY
|
|
10820
10821
|
|
|
@@ -11056,6 +11057,11 @@ function isVoidPseudoAgentReference(rawReference) {
|
|
|
11056
11057
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
11057
11058
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
11058
11059
|
*
|
|
11060
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
11061
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
11062
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
11063
|
+
* outbound request.
|
|
11064
|
+
*
|
|
11059
11065
|
* @public exported from `@promptbook/utils`
|
|
11060
11066
|
*/
|
|
11061
11067
|
function isValidAgentUrl(url) {
|
|
@@ -11069,12 +11075,6 @@ function isValidAgentUrl(url) {
|
|
|
11069
11075
|
// TODO: [🐠]
|
|
11070
11076
|
return false;
|
|
11071
11077
|
}
|
|
11072
|
-
/*
|
|
11073
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
11074
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
11075
|
-
return false;
|
|
11076
|
-
}
|
|
11077
|
-
*/
|
|
11078
11078
|
return true;
|
|
11079
11079
|
}
|
|
11080
11080
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -11250,8 +11250,11 @@ class GoalCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
11250
11250
|
if (!trimmedContent) {
|
|
11251
11251
|
return requirements;
|
|
11252
11252
|
}
|
|
11253
|
-
|
|
11254
|
-
|
|
11253
|
+
const goalSection = spaceTrim$1((block) => `
|
|
11254
|
+
## Goal
|
|
11255
|
+
|
|
11256
|
+
${block(trimmedContent)}
|
|
11257
|
+
`);
|
|
11255
11258
|
const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
|
|
11256
11259
|
return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
|
|
11257
11260
|
}
|
|
@@ -11773,8 +11776,11 @@ class LanguageCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
11773
11776
|
if (!trimmedContent) {
|
|
11774
11777
|
return requirements;
|
|
11775
11778
|
}
|
|
11776
|
-
|
|
11777
|
-
|
|
11779
|
+
const languageSection = spaceTrim$1((block) => `
|
|
11780
|
+
## Language
|
|
11781
|
+
|
|
11782
|
+
- Your language is ${block(trimmedContent)}
|
|
11783
|
+
`);
|
|
11778
11784
|
return this.appendToSystemMessage(requirements, languageSection, '\n\n');
|
|
11779
11785
|
}
|
|
11780
11786
|
}
|
|
@@ -14240,21 +14246,22 @@ function getPointBounds(points) {
|
|
|
14240
14246
|
* @private helper of `fractalAvatarVisual`
|
|
14241
14247
|
*/
|
|
14242
14248
|
function drawDragonCurveLayer(context, points, options) {
|
|
14243
|
-
const {
|
|
14249
|
+
const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
|
|
14244
14250
|
const firstPoint = points[0];
|
|
14245
14251
|
const lastPoint = points[points.length - 1];
|
|
14246
14252
|
const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
|
|
14247
14253
|
ribbonGradient.addColorStop(0, `${primaryColor}f2`);
|
|
14248
14254
|
ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
|
|
14249
14255
|
ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
|
|
14256
|
+
// Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
|
|
14257
|
+
// context.filter blur, which triggers a costly software rasterization pass every frame.
|
|
14250
14258
|
context.save();
|
|
14251
14259
|
context.beginPath();
|
|
14252
14260
|
tracePolyline(context, points);
|
|
14253
|
-
context.strokeStyle = `${shadowColor}
|
|
14254
|
-
context.lineWidth = strokeWidth *
|
|
14261
|
+
context.strokeStyle = `${shadowColor}48`;
|
|
14262
|
+
context.lineWidth = strokeWidth * 4.5;
|
|
14255
14263
|
context.lineJoin = 'round';
|
|
14256
14264
|
context.lineCap = 'round';
|
|
14257
|
-
context.filter = `blur(${size * 0.022}px)`;
|
|
14258
14265
|
context.stroke();
|
|
14259
14266
|
context.restore();
|
|
14260
14267
|
context.beginPath();
|
|
@@ -14716,7 +14723,7 @@ function fillTextureRect(texture, x, y, width, height, color) {
|
|
|
14716
14723
|
*
|
|
14717
14724
|
* @private helper of `minecraft2AvatarVisual`
|
|
14718
14725
|
*/
|
|
14719
|
-
const LIGHT_DIRECTION$
|
|
14726
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
14720
14727
|
x: 0.4,
|
|
14721
14728
|
y: -0.65,
|
|
14722
14729
|
z: 0.92,
|
|
@@ -14838,11 +14845,23 @@ function drawMinecraftBackdrop(context, size, palette, sceneCenterX, spotlightY,
|
|
|
14838
14845
|
* @private helper of `minecraft2AvatarVisual`
|
|
14839
14846
|
*/
|
|
14840
14847
|
function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
|
|
14848
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.03;
|
|
14849
|
+
const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
|
|
14850
|
+
const rx = size * (0.16 + interaction.intensity * 0.015);
|
|
14851
|
+
const ry = size * 0.055;
|
|
14852
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
14841
14853
|
context.save();
|
|
14842
|
-
context.
|
|
14843
|
-
context.
|
|
14854
|
+
context.translate(cx, cy);
|
|
14855
|
+
context.scale(1, ry / rx);
|
|
14856
|
+
const blurRadius = rx * 1.4;
|
|
14857
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
14858
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
14859
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
14860
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
14861
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
14862
|
+
context.fillStyle = shadowGradient;
|
|
14844
14863
|
context.beginPath();
|
|
14845
|
-
context.
|
|
14864
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
14846
14865
|
context.fill();
|
|
14847
14866
|
context.restore();
|
|
14848
14867
|
}
|
|
@@ -14928,7 +14947,7 @@ function resolveVisibleCuboidFaces(cuboid, size, sceneCenterX, sceneCenterY) {
|
|
|
14928
14947
|
corners: projectedCorners,
|
|
14929
14948
|
texture: faceDefinition.texture,
|
|
14930
14949
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
14931
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
14950
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
14932
14951
|
outlineColor: cuboid.outlineColor,
|
|
14933
14952
|
};
|
|
14934
14953
|
});
|
|
@@ -15066,13 +15085,27 @@ const minecraftAvatarVisual = {
|
|
|
15066
15085
|
spotlight.addColorStop(1, `${palette.highlight}00`);
|
|
15067
15086
|
context.fillStyle = spotlight;
|
|
15068
15087
|
context.fillRect(0, 0, size, size);
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15088
|
+
{
|
|
15089
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
15090
|
+
const cx = size * 0.5;
|
|
15091
|
+
const cy = size * 0.86;
|
|
15092
|
+
const rx = size * 0.2;
|
|
15093
|
+
const ry = size * 0.06;
|
|
15094
|
+
const blurRadius = rx * 1.4;
|
|
15095
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
15096
|
+
shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
|
|
15097
|
+
shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
|
|
15098
|
+
shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
|
|
15099
|
+
shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
15100
|
+
context.save();
|
|
15101
|
+
context.translate(cx, cy);
|
|
15102
|
+
context.scale(1, ry / rx);
|
|
15103
|
+
context.fillStyle = shadowGradient;
|
|
15104
|
+
context.beginPath();
|
|
15105
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
15106
|
+
context.fill();
|
|
15107
|
+
context.restore();
|
|
15108
|
+
}
|
|
15076
15109
|
drawVoxelCuboid(context, {
|
|
15077
15110
|
x: bodyX,
|
|
15078
15111
|
y: bodyY,
|
|
@@ -15601,7 +15634,7 @@ function resolveSeededIntegerRange(random, minimumValue, maximumValue) {
|
|
|
15601
15634
|
*
|
|
15602
15635
|
* @private helper of `octopus3AvatarVisual`
|
|
15603
15636
|
*/
|
|
15604
|
-
function formatAlphaHex$
|
|
15637
|
+
function formatAlphaHex$2(opacity) {
|
|
15605
15638
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
15606
15639
|
.toString(16)
|
|
15607
15640
|
.padStart(2, '0');
|
|
@@ -15980,7 +16013,7 @@ function drawSeededEye(context, centerX, centerY, radiusX, radiusY, rotation, pa
|
|
|
15980
16013
|
context.beginPath();
|
|
15981
16014
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
15982
16015
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
15983
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
16016
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
15984
16017
|
context.lineWidth = radiusX * 0.08;
|
|
15985
16018
|
context.lineCap = 'round';
|
|
15986
16019
|
context.stroke();
|
|
@@ -16061,7 +16094,7 @@ function drawProjectedOrganicEye(context, localCenter, radiusX, radiusY, center,
|
|
|
16061
16094
|
context.beginPath();
|
|
16062
16095
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16063
16096
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16064
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
16097
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
16065
16098
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
16066
16099
|
context.lineCap = 'round';
|
|
16067
16100
|
context.stroke();
|
|
@@ -16107,7 +16140,7 @@ function drawProjectedQuad(context, corners, fillStyle) {
|
|
|
16107
16140
|
*
|
|
16108
16141
|
* @private helper of the 3D octopus avatar visuals
|
|
16109
16142
|
*/
|
|
16110
|
-
function formatAlphaHex(opacity) {
|
|
16143
|
+
function formatAlphaHex$1(opacity) {
|
|
16111
16144
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
16112
16145
|
.toString(16)
|
|
16113
16146
|
.padStart(2, '0');
|
|
@@ -16119,11 +16152,40 @@ function formatAlphaHex(opacity) {
|
|
|
16119
16152
|
*
|
|
16120
16153
|
* @private helper of `octopus3dAvatarVisual`
|
|
16121
16154
|
*/
|
|
16122
|
-
const LIGHT_DIRECTION$
|
|
16155
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
16123
16156
|
x: 0.48,
|
|
16124
16157
|
y: -0.62,
|
|
16125
16158
|
z: 0.94,
|
|
16126
16159
|
});
|
|
16160
|
+
/**
|
|
16161
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
16162
|
+
*
|
|
16163
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
16164
|
+
*/
|
|
16165
|
+
const octopus3dStableStateCache = new WeakMap();
|
|
16166
|
+
/**
|
|
16167
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
16168
|
+
*
|
|
16169
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
16170
|
+
*/
|
|
16171
|
+
function getOctopus3dStableState(createRandom) {
|
|
16172
|
+
const cached = octopus3dStableStateCache.get(createRandom);
|
|
16173
|
+
if (cached !== undefined) {
|
|
16174
|
+
return cached;
|
|
16175
|
+
}
|
|
16176
|
+
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
16177
|
+
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
16178
|
+
const leftEyePhaseOffset = eyeRandom() * 0.6;
|
|
16179
|
+
const rightEyePhaseOffset = eyeRandom() * 0.6;
|
|
16180
|
+
const state = {
|
|
16181
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
16182
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16183
|
+
leftEyePhaseOffset,
|
|
16184
|
+
rightEyePhaseOffset,
|
|
16185
|
+
};
|
|
16186
|
+
octopus3dStableStateCache.set(createRandom, state);
|
|
16187
|
+
return state;
|
|
16188
|
+
}
|
|
16127
16189
|
/**
|
|
16128
16190
|
* Proper 3D Octopus visual built from projected organic meshes and tentacles.
|
|
16129
16191
|
*
|
|
@@ -16136,10 +16198,7 @@ const octopus3dAvatarVisual = {
|
|
|
16136
16198
|
isAnimated: true,
|
|
16137
16199
|
supportsPointerTracking: true,
|
|
16138
16200
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
16139
|
-
const morphologyProfile =
|
|
16140
|
-
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
16141
|
-
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
16142
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
16201
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
|
|
16143
16202
|
const sceneCenterX = size * 0.5;
|
|
16144
16203
|
const sceneCenterY = size * 0.56;
|
|
16145
16204
|
const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
|
|
@@ -16236,12 +16295,12 @@ const octopus3dAvatarVisual = {
|
|
|
16236
16295
|
x: -faceEyeSpacing,
|
|
16237
16296
|
y: faceEyeYOffset,
|
|
16238
16297
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
|
|
16239
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
16298
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16240
16299
|
drawProjectedOrganicEye(context, {
|
|
16241
16300
|
x: faceEyeSpacing,
|
|
16242
16301
|
y: faceEyeYOffset,
|
|
16243
16302
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
|
|
16244
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 +
|
|
16303
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16245
16304
|
drawProjectedOrganicMouth(context, [
|
|
16246
16305
|
{
|
|
16247
16306
|
x: -mouthHalfWidth,
|
|
@@ -16285,14 +16344,28 @@ function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCent
|
|
|
16285
16344
|
/**
|
|
16286
16345
|
* Draws the soft ground shadow below the octopus.
|
|
16287
16346
|
*
|
|
16347
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
16348
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
16349
|
+
*
|
|
16288
16350
|
* @private helper of `octopus3dAvatarVisual`
|
|
16289
16351
|
*/
|
|
16290
16352
|
function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
|
|
16353
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.04;
|
|
16354
|
+
const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
|
|
16355
|
+
const rx = size * (0.18 + interaction.intensity * 0.02);
|
|
16356
|
+
const ry = size * 0.06;
|
|
16291
16357
|
context.save();
|
|
16292
|
-
context.
|
|
16293
|
-
context.
|
|
16358
|
+
context.translate(cx, cy);
|
|
16359
|
+
context.scale(1, ry / rx);
|
|
16360
|
+
const blurRadius = rx * 1.4;
|
|
16361
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
16362
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
16363
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
16364
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
16365
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
16366
|
+
context.fillStyle = shadowGradient;
|
|
16294
16367
|
context.beginPath();
|
|
16295
|
-
context.
|
|
16368
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
16296
16369
|
context.fill();
|
|
16297
16370
|
context.restore();
|
|
16298
16371
|
}
|
|
@@ -16329,7 +16402,7 @@ function resolveVisibleEllipsoidPatches(options) {
|
|
|
16329
16402
|
corners: projectedCorners,
|
|
16330
16403
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
16331
16404
|
transformedCorners.length,
|
|
16332
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
16405
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
16333
16406
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
16334
16407
|
outlineColor,
|
|
16335
16408
|
});
|
|
@@ -16518,11 +16591,40 @@ function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
|
|
|
16518
16591
|
*
|
|
16519
16592
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16520
16593
|
*/
|
|
16521
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
16594
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
16522
16595
|
x: 0.38,
|
|
16523
16596
|
y: -0.6,
|
|
16524
16597
|
z: 0.98,
|
|
16525
16598
|
});
|
|
16599
|
+
/**
|
|
16600
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
16601
|
+
*
|
|
16602
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16603
|
+
*/
|
|
16604
|
+
const octopus3d2StableStateCache = new WeakMap();
|
|
16605
|
+
/**
|
|
16606
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
16607
|
+
*
|
|
16608
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16609
|
+
*/
|
|
16610
|
+
function getOctopus3d2StableState(createRandom) {
|
|
16611
|
+
const cached = octopus3d2StableStateCache.get(createRandom);
|
|
16612
|
+
if (cached !== undefined) {
|
|
16613
|
+
return cached;
|
|
16614
|
+
}
|
|
16615
|
+
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
16616
|
+
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
16617
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
16618
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
16619
|
+
const state = {
|
|
16620
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
16621
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16622
|
+
leftEyePhaseOffset,
|
|
16623
|
+
rightEyePhaseOffset,
|
|
16624
|
+
};
|
|
16625
|
+
octopus3d2StableStateCache.set(createRandom, state);
|
|
16626
|
+
return state;
|
|
16627
|
+
}
|
|
16526
16628
|
/**
|
|
16527
16629
|
* Octopus 3D 2 avatar visual.
|
|
16528
16630
|
*
|
|
@@ -16535,10 +16637,7 @@ const octopus3d2AvatarVisual = {
|
|
|
16535
16637
|
isAnimated: true,
|
|
16536
16638
|
supportsPointerTracking: true,
|
|
16537
16639
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
16538
|
-
const morphologyProfile =
|
|
16539
|
-
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
16540
|
-
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
16541
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
16640
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
|
|
16542
16641
|
const sceneCenterX = size * 0.5;
|
|
16543
16642
|
const sceneCenterY = size * 0.575;
|
|
16544
16643
|
const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
|
|
@@ -16591,8 +16690,8 @@ const octopus3d2AvatarVisual = {
|
|
|
16591
16690
|
const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
|
|
16592
16691
|
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
16593
16692
|
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
16594
|
-
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
16595
|
-
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 +
|
|
16693
|
+
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16694
|
+
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16596
16695
|
drawProjectedOrganicMouth(context, [
|
|
16597
16696
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
16598
16697
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
@@ -16621,54 +16720,118 @@ function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, scene
|
|
|
16621
16720
|
/**
|
|
16622
16721
|
* Draws the soft floor shadow that anchors the single mesh in the frame.
|
|
16623
16722
|
*
|
|
16723
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
16724
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
16725
|
+
*
|
|
16624
16726
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16625
16727
|
*/
|
|
16626
16728
|
function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
16729
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
16730
|
+
const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
|
|
16731
|
+
const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
|
|
16732
|
+
const ry = size * 0.062;
|
|
16627
16733
|
context.save();
|
|
16628
|
-
context.
|
|
16629
|
-
context.
|
|
16734
|
+
context.translate(cx, cy);
|
|
16735
|
+
context.scale(1, ry / rx);
|
|
16736
|
+
const blurRadius = rx * 1.4;
|
|
16737
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
16738
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
16739
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
16740
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
16741
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
16742
|
+
context.fillStyle = shadowGradient;
|
|
16630
16743
|
context.beginPath();
|
|
16631
|
-
context.
|
|
16744
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
16632
16745
|
context.fill();
|
|
16633
16746
|
context.restore();
|
|
16634
16747
|
}
|
|
16748
|
+
/**
|
|
16749
|
+
* Number of latitude segments used by the single blobby octopus mesh.
|
|
16750
|
+
*
|
|
16751
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16752
|
+
*/
|
|
16753
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
16754
|
+
/**
|
|
16755
|
+
* Number of longitude segments used by the single blobby octopus mesh.
|
|
16756
|
+
*
|
|
16757
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16758
|
+
*/
|
|
16759
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
16635
16760
|
/**
|
|
16636
16761
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
16637
16762
|
*
|
|
16763
|
+
* Within a single frame, mesh corner samples and longitude-only lobe-wave values are
|
|
16764
|
+
* quantized to the patch grid and computed once each rather than re-evaluated for every
|
|
16765
|
+
* patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
|
|
16766
|
+
* `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
|
|
16767
|
+
* corners are shared between neighboring patches.
|
|
16768
|
+
*
|
|
16638
16769
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16639
16770
|
*/
|
|
16640
16771
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
16641
16772
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
16642
|
-
const latitudePatchCount =
|
|
16643
|
-
const longitudePatchCount =
|
|
16773
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
16774
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
16644
16775
|
const surfacePatches = [];
|
|
16776
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16777
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
16778
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
16779
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
16780
|
+
}
|
|
16781
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16782
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
16783
|
+
}
|
|
16784
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
16785
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16786
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
|
|
16787
|
+
}
|
|
16788
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
16789
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
16790
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
16791
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
16792
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
16793
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
16794
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
16795
|
+
const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
16796
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
16797
|
+
}
|
|
16798
|
+
}
|
|
16645
16799
|
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
16646
|
-
const startLatitude =
|
|
16647
|
-
const endLatitude =
|
|
16800
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
16801
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
16648
16802
|
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
16649
16803
|
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
16804
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
16805
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
16650
16806
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16651
|
-
const startLongitude =
|
|
16652
|
-
const endLongitude =
|
|
16807
|
+
const startLongitude = longitudeBoundaries[longitudeIndex];
|
|
16808
|
+
const endLongitude = longitudeBoundaries[longitudeIndex + 1];
|
|
16653
16809
|
const centerLongitude = (startLongitude + endLongitude) / 2;
|
|
16654
|
-
const
|
|
16655
|
-
|
|
16656
|
-
|
|
16657
|
-
|
|
16658
|
-
|
|
16810
|
+
const transformedCorners = [
|
|
16811
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
16812
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
16813
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
16814
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
16659
16815
|
];
|
|
16660
|
-
const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
|
|
16661
16816
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
16662
16817
|
if (surfaceNormal.z <= 0.01) {
|
|
16663
16818
|
continue;
|
|
16664
16819
|
}
|
|
16665
|
-
const projectedCorners =
|
|
16820
|
+
const projectedCorners = [
|
|
16821
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
16822
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
16823
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
16824
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
16825
|
+
];
|
|
16666
16826
|
surfacePatches.push({
|
|
16667
16827
|
corners: projectedCorners,
|
|
16668
|
-
averageDepth: transformedCorners.
|
|
16669
|
-
transformedCorners.
|
|
16670
|
-
|
|
16671
|
-
|
|
16828
|
+
averageDepth: (transformedCorners[0].z +
|
|
16829
|
+
transformedCorners[1].z +
|
|
16830
|
+
transformedCorners[2].z +
|
|
16831
|
+
transformedCorners[3].z) /
|
|
16832
|
+
4,
|
|
16833
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
16834
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
16672
16835
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
16673
16836
|
});
|
|
16674
16837
|
}
|
|
@@ -16684,12 +16847,21 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
16684
16847
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16685
16848
|
*/
|
|
16686
16849
|
function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
|
|
16850
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
16851
|
+
return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
|
|
16852
|
+
}
|
|
16853
|
+
/**
|
|
16854
|
+
* Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
|
|
16855
|
+
* to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
|
|
16856
|
+
*
|
|
16857
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16858
|
+
*/
|
|
16859
|
+
function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
|
|
16687
16860
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
16688
16861
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
16689
16862
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
16690
16863
|
const upperBlend = Math.pow(1 - verticalProgress, 1.2);
|
|
16691
16864
|
const lowerBlend = Math.pow(verticalProgress, 1.42);
|
|
16692
|
-
const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
|
|
16693
16865
|
const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
|
|
16694
16866
|
const horizontalScale = 1.02 +
|
|
16695
16867
|
skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
|
|
@@ -16727,7 +16899,7 @@ function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, time
|
|
|
16727
16899
|
*
|
|
16728
16900
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16729
16901
|
*/
|
|
16730
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16902
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16731
16903
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
16732
16904
|
if (tonalProgress < 0.16) {
|
|
16733
16905
|
return palette.highlight;
|
|
@@ -16767,6 +16939,1384 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
|
|
|
16767
16939
|
context.restore();
|
|
16768
16940
|
}
|
|
16769
16941
|
|
|
16942
|
+
/* eslint-disable no-magic-numbers */
|
|
16943
|
+
/**
|
|
16944
|
+
* Light direction used by the continuous octopus mesh shading.
|
|
16945
|
+
*
|
|
16946
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16947
|
+
*/
|
|
16948
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
16949
|
+
x: 0.34,
|
|
16950
|
+
y: -0.62,
|
|
16951
|
+
z: 1,
|
|
16952
|
+
});
|
|
16953
|
+
/**
|
|
16954
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
16955
|
+
*
|
|
16956
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16957
|
+
*/
|
|
16958
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
16959
|
+
/**
|
|
16960
|
+
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
16961
|
+
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
16962
|
+
* React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
|
|
16963
|
+
*
|
|
16964
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16965
|
+
*/
|
|
16966
|
+
const stableStateCache = new WeakMap();
|
|
16967
|
+
/**
|
|
16968
|
+
* Returns the stable per-avatar state, computing it on first access and returning the cached
|
|
16969
|
+
* result on every subsequent call within the same `<Avatar/>` mount.
|
|
16970
|
+
*
|
|
16971
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16972
|
+
*/
|
|
16973
|
+
function getOctopus3d3StableState(createRandom) {
|
|
16974
|
+
const cached = stableStateCache.get(createRandom);
|
|
16975
|
+
if (cached !== undefined) {
|
|
16976
|
+
return cached;
|
|
16977
|
+
}
|
|
16978
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
16979
|
+
const animationRandom = createRandom('octopus3d3-animation-profile');
|
|
16980
|
+
const eyeRandom = createRandom('octopus3d3-eye-profile');
|
|
16981
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
16982
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
16983
|
+
const state = {
|
|
16984
|
+
morphologyProfile,
|
|
16985
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16986
|
+
leftEyePhaseOffset,
|
|
16987
|
+
rightEyePhaseOffset,
|
|
16988
|
+
tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
16989
|
+
};
|
|
16990
|
+
stableStateCache.set(createRandom, state);
|
|
16991
|
+
return state;
|
|
16992
|
+
}
|
|
16993
|
+
/**
|
|
16994
|
+
* Octopus 3D 3 avatar visual.
|
|
16995
|
+
*
|
|
16996
|
+
* @private built-in avatar visual
|
|
16997
|
+
*/
|
|
16998
|
+
const octopus3d3AvatarVisual = {
|
|
16999
|
+
id: 'octopus3d3',
|
|
17000
|
+
title: 'Octopus 3D 3',
|
|
17001
|
+
description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
|
|
17002
|
+
isAnimated: true,
|
|
17003
|
+
supportsPointerTracking: true,
|
|
17004
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
17005
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
|
|
17006
|
+
const sceneCenterX = size * 0.5;
|
|
17007
|
+
const sceneCenterY = size * 0.535;
|
|
17008
|
+
const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
|
|
17009
|
+
const meshCenter = {
|
|
17010
|
+
x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
17011
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
17012
|
+
z: interaction.intensity * size * 0.018,
|
|
17013
|
+
};
|
|
17014
|
+
const rotationY = -0.1 +
|
|
17015
|
+
Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
|
|
17016
|
+
interaction.bodyOffsetX * 0.22 +
|
|
17017
|
+
interaction.gazeX * 0.88;
|
|
17018
|
+
const rotationX = -0.07 +
|
|
17019
|
+
Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
|
|
17020
|
+
interaction.bodyOffsetY * 0.08 -
|
|
17021
|
+
interaction.gazeY * 0.38;
|
|
17022
|
+
const surfaceOptions = {
|
|
17023
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
|
|
17024
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
|
|
17025
|
+
radiusZ: size *
|
|
17026
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
17027
|
+
(1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
|
|
17028
|
+
morphologyProfile,
|
|
17029
|
+
timeMs,
|
|
17030
|
+
animationPhase,
|
|
17031
|
+
tentacleProfiles,
|
|
17032
|
+
};
|
|
17033
|
+
const surfacePatches = resolveVisibleContinuousOctopusPatches({
|
|
17034
|
+
...surfaceOptions,
|
|
17035
|
+
center: meshCenter,
|
|
17036
|
+
rotationX,
|
|
17037
|
+
rotationY,
|
|
17038
|
+
sceneCenterX,
|
|
17039
|
+
sceneCenterY,
|
|
17040
|
+
size,
|
|
17041
|
+
palette,
|
|
17042
|
+
});
|
|
17043
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
|
|
17044
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
|
|
17045
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
17046
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
17047
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
17048
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
17049
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
|
|
17050
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
|
|
17051
|
+
drawAvatarFrame(context, size, palette);
|
|
17052
|
+
drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
17053
|
+
drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
17054
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
17055
|
+
drawContinuousSurfacePatch(context, surfacePatch);
|
|
17056
|
+
}
|
|
17057
|
+
drawProjectedSurfaceCurrents({
|
|
17058
|
+
context,
|
|
17059
|
+
surfaceOptions,
|
|
17060
|
+
center: meshCenter,
|
|
17061
|
+
rotationX,
|
|
17062
|
+
rotationY,
|
|
17063
|
+
sceneCenterX,
|
|
17064
|
+
sceneCenterY,
|
|
17065
|
+
size,
|
|
17066
|
+
palette,
|
|
17067
|
+
morphologyProfile,
|
|
17068
|
+
timeMs,
|
|
17069
|
+
animationPhase,
|
|
17070
|
+
});
|
|
17071
|
+
drawProjectedTentacleSuckers({
|
|
17072
|
+
context,
|
|
17073
|
+
surfaceOptions,
|
|
17074
|
+
center: meshCenter,
|
|
17075
|
+
rotationX,
|
|
17076
|
+
rotationY,
|
|
17077
|
+
sceneCenterX,
|
|
17078
|
+
sceneCenterY,
|
|
17079
|
+
size,
|
|
17080
|
+
palette,
|
|
17081
|
+
});
|
|
17082
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17083
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17084
|
+
drawProjectedOrganicMouth(context, [
|
|
17085
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
17086
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
17087
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
17088
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
17089
|
+
},
|
|
17090
|
+
};
|
|
17091
|
+
/**
|
|
17092
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
17093
|
+
*
|
|
17094
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17095
|
+
*/
|
|
17096
|
+
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17097
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
17098
|
+
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
17099
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
17100
|
+
return {
|
|
17101
|
+
centerLongitude: -Math.PI * 0.86 +
|
|
17102
|
+
progress * Math.PI * 1.72 +
|
|
17103
|
+
(tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
|
|
17104
|
+
widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
|
|
17105
|
+
lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
|
|
17106
|
+
swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
|
|
17107
|
+
depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
|
|
17108
|
+
phase: tentacleRandom() * Math.PI * 2,
|
|
17109
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
17110
|
+
};
|
|
17111
|
+
});
|
|
17112
|
+
}
|
|
17113
|
+
/**
|
|
17114
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
17115
|
+
*
|
|
17116
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17117
|
+
*/
|
|
17118
|
+
function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
17119
|
+
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));
|
|
17120
|
+
glowGradient.addColorStop(0, `${palette.highlight}66`);
|
|
17121
|
+
glowGradient.addColorStop(0.34, `${palette.accent}2e`);
|
|
17122
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
17123
|
+
context.fillStyle = glowGradient;
|
|
17124
|
+
context.fillRect(0, 0, size, size);
|
|
17125
|
+
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);
|
|
17126
|
+
lowerGradient.addColorStop(0, `${palette.secondary}25`);
|
|
17127
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
17128
|
+
context.fillStyle = lowerGradient;
|
|
17129
|
+
context.fillRect(0, 0, size, size);
|
|
17130
|
+
}
|
|
17131
|
+
/**
|
|
17132
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
17133
|
+
*
|
|
17134
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
17135
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
17136
|
+
*
|
|
17137
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17138
|
+
*/
|
|
17139
|
+
function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
17140
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
17141
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
17142
|
+
const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
|
|
17143
|
+
const ry = size * 0.06;
|
|
17144
|
+
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
17145
|
+
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
17146
|
+
context.save();
|
|
17147
|
+
context.translate(cx, cy);
|
|
17148
|
+
context.scale(1, ry / rx);
|
|
17149
|
+
const blurRadius = rx * 1.4;
|
|
17150
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
17151
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
17152
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
17153
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
17154
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
17155
|
+
context.fillStyle = shadowGradient;
|
|
17156
|
+
context.beginPath();
|
|
17157
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
17158
|
+
context.fill();
|
|
17159
|
+
context.restore();
|
|
17160
|
+
}
|
|
17161
|
+
/**
|
|
17162
|
+
* Number of latitude segments used by the continuous Octopus 3D 3 mesh.
|
|
17163
|
+
*
|
|
17164
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17165
|
+
*/
|
|
17166
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
17167
|
+
/**
|
|
17168
|
+
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
17169
|
+
*
|
|
17170
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17171
|
+
*/
|
|
17172
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
17173
|
+
/**
|
|
17174
|
+
* Resolves visible projected patches for the continuous octopus mesh.
|
|
17175
|
+
*
|
|
17176
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
17177
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
17178
|
+
* than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
|
|
17179
|
+
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
17180
|
+
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
17181
|
+
*
|
|
17182
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17183
|
+
*/
|
|
17184
|
+
function resolveVisibleContinuousOctopusPatches(options) {
|
|
17185
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17186
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
17187
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
17188
|
+
const surfacePatches = [];
|
|
17189
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17190
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
17191
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
17192
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
17193
|
+
}
|
|
17194
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17195
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
17196
|
+
}
|
|
17197
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
17198
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
17199
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
17200
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17201
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17202
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17203
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
17204
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
|
|
17205
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
|
|
17206
|
+
}
|
|
17207
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17208
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
17209
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
|
|
17210
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
|
|
17211
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
17212
|
+
}
|
|
17213
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
17214
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
17215
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
17216
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
17217
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
17218
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
17219
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
17220
|
+
const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
17221
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
17222
|
+
}
|
|
17223
|
+
}
|
|
17224
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
17225
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
17226
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
17227
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
17228
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
17229
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
17230
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
17231
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17232
|
+
const transformedCorners = [
|
|
17233
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
17234
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
17235
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
17236
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
17237
|
+
];
|
|
17238
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
17239
|
+
if (surfaceNormal.z <= 0.008) {
|
|
17240
|
+
continue;
|
|
17241
|
+
}
|
|
17242
|
+
const projectedCorners = [
|
|
17243
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
17244
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
17245
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
17246
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
17247
|
+
];
|
|
17248
|
+
surfacePatches.push({
|
|
17249
|
+
corners: projectedCorners,
|
|
17250
|
+
averageDepth: (transformedCorners[0].z +
|
|
17251
|
+
transformedCorners[1].z +
|
|
17252
|
+
transformedCorners[2].z +
|
|
17253
|
+
transformedCorners[3].z) /
|
|
17254
|
+
4,
|
|
17255
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
17256
|
+
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17257
|
+
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
17258
|
+
});
|
|
17259
|
+
}
|
|
17260
|
+
}
|
|
17261
|
+
return surfacePatches;
|
|
17262
|
+
}
|
|
17263
|
+
/**
|
|
17264
|
+
* Samples one point on the continuous Octopus 3D 3 surface.
|
|
17265
|
+
*
|
|
17266
|
+
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
17267
|
+
* tentacled while still being rendered as one connected blobby mesh.
|
|
17268
|
+
*
|
|
17269
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17270
|
+
*/
|
|
17271
|
+
function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
|
|
17272
|
+
return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
|
|
17273
|
+
}
|
|
17274
|
+
/**
|
|
17275
|
+
* Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
|
|
17276
|
+
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
17277
|
+
*
|
|
17278
|
+
* The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
|
|
17279
|
+
* corner grid, so the same longitude is reused across every latitude row and each
|
|
17280
|
+
* tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
|
|
17281
|
+
*
|
|
17282
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17283
|
+
*/
|
|
17284
|
+
function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
17285
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
17286
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17287
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17288
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
17289
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
17290
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
17291
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
17292
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
17293
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17294
|
+
animationPhase * 0.6 +
|
|
17295
|
+
timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
|
|
17296
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
|
|
17297
|
+
(0.3 + lowerBlend * 0.7);
|
|
17298
|
+
const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
|
|
17299
|
+
lowerBlend *
|
|
17300
|
+
tentacleInfluence.core *
|
|
17301
|
+
tentacleInfluence.swayScale;
|
|
17302
|
+
const horizontalScale = 1.04 +
|
|
17303
|
+
mantleRipple +
|
|
17304
|
+
lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
|
|
17305
|
+
lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
|
|
17306
|
+
upperBlend * 0.08;
|
|
17307
|
+
const depthScale = 1.06 +
|
|
17308
|
+
upperBlend * 0.16 +
|
|
17309
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
|
|
17310
|
+
lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
|
|
17311
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
17312
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
|
|
17313
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
17314
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
|
|
17315
|
+
const lowerDrop = lowerBlend *
|
|
17316
|
+
radiusY *
|
|
17317
|
+
(0.18 +
|
|
17318
|
+
tentacleInfluence.core *
|
|
17319
|
+
(0.38 +
|
|
17320
|
+
tentacleInfluence.lengthScale * 0.22 +
|
|
17321
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
|
|
17322
|
+
return {
|
|
17323
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
|
|
17324
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
|
|
17325
|
+
upperBlend * radiusY * 0.1 +
|
|
17326
|
+
lowerDrop +
|
|
17327
|
+
Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
|
|
17328
|
+
Math.cos(timeMs / 880 + tentacleInfluence.phase) *
|
|
17329
|
+
lowerBlend *
|
|
17330
|
+
tipBlend *
|
|
17331
|
+
tentacleInfluence.core *
|
|
17332
|
+
radiusY *
|
|
17333
|
+
0.034,
|
|
17334
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
17335
|
+
Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
|
|
17336
|
+
lowerBlend *
|
|
17337
|
+
tentacleInfluence.core *
|
|
17338
|
+
radiusZ *
|
|
17339
|
+
0.04,
|
|
17340
|
+
};
|
|
17341
|
+
}
|
|
17342
|
+
/**
|
|
17343
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
17344
|
+
*
|
|
17345
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17346
|
+
*/
|
|
17347
|
+
function resolveContinuousTentacleInfluence(options, longitude) {
|
|
17348
|
+
let totalWeight = 0;
|
|
17349
|
+
let weightedSin = 0;
|
|
17350
|
+
let weightedCos = 0;
|
|
17351
|
+
let weightedWidthScale = 0;
|
|
17352
|
+
let weightedLengthScale = 0;
|
|
17353
|
+
let weightedSwayScale = 0;
|
|
17354
|
+
let weightedDepthScale = 0;
|
|
17355
|
+
let weightedPhase = 0;
|
|
17356
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
17357
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
17358
|
+
const width = 0.2 * tentacleProfile.widthScale;
|
|
17359
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
17360
|
+
totalWeight += weight;
|
|
17361
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
17362
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
17363
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
17364
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
17365
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
17366
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
17367
|
+
weightedPhase += tentacleProfile.phase * weight;
|
|
17368
|
+
}
|
|
17369
|
+
if (totalWeight < 0.0001) {
|
|
17370
|
+
return {
|
|
17371
|
+
core: 0,
|
|
17372
|
+
centerLongitude: longitude,
|
|
17373
|
+
widthScale: 1,
|
|
17374
|
+
lengthScale: 1,
|
|
17375
|
+
swayScale: 1,
|
|
17376
|
+
depthScale: 1,
|
|
17377
|
+
phase: 0,
|
|
17378
|
+
};
|
|
17379
|
+
}
|
|
17380
|
+
return {
|
|
17381
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
17382
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
17383
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
17384
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
17385
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
17386
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
17387
|
+
phase: weightedPhase / totalWeight,
|
|
17388
|
+
};
|
|
17389
|
+
}
|
|
17390
|
+
/**
|
|
17391
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
17392
|
+
*
|
|
17393
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17394
|
+
*/
|
|
17395
|
+
function resolveContinuousLobeWave(options, longitude) {
|
|
17396
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
17397
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
17398
|
+
animationPhase +
|
|
17399
|
+
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
17400
|
+
1) /
|
|
17401
|
+
2);
|
|
17402
|
+
}
|
|
17403
|
+
/**
|
|
17404
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
17405
|
+
*
|
|
17406
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17407
|
+
*/
|
|
17408
|
+
function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
17409
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
17410
|
+
if (tonalProgress < 0.14) {
|
|
17411
|
+
return palette.highlight;
|
|
17412
|
+
}
|
|
17413
|
+
if (tonalProgress < 0.32) {
|
|
17414
|
+
return palette.secondary;
|
|
17415
|
+
}
|
|
17416
|
+
if (tonalProgress < 0.72) {
|
|
17417
|
+
return forwardness > 0.55 ? palette.secondary : palette.primary;
|
|
17418
|
+
}
|
|
17419
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
17420
|
+
}
|
|
17421
|
+
/**
|
|
17422
|
+
* Draws one projected mesh patch with soft shading and a subtle edge.
|
|
17423
|
+
*
|
|
17424
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17425
|
+
*/
|
|
17426
|
+
function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
17427
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
17428
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
17429
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
|
|
17430
|
+
}
|
|
17431
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
17432
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
17433
|
+
}
|
|
17434
|
+
context.save();
|
|
17435
|
+
context.beginPath();
|
|
17436
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
17437
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
17438
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
17439
|
+
}
|
|
17440
|
+
context.closePath();
|
|
17441
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
17442
|
+
context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
|
|
17443
|
+
context.lineJoin = 'round';
|
|
17444
|
+
context.stroke();
|
|
17445
|
+
context.restore();
|
|
17446
|
+
}
|
|
17447
|
+
/**
|
|
17448
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
17449
|
+
*
|
|
17450
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17451
|
+
*/
|
|
17452
|
+
function drawProjectedSurfaceCurrents(options) {
|
|
17453
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
17454
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
17455
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
17456
|
+
context.save();
|
|
17457
|
+
context.lineCap = 'round';
|
|
17458
|
+
context.lineJoin = 'round';
|
|
17459
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
17460
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
17461
|
+
const projectedPoints = [];
|
|
17462
|
+
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
|
|
17463
|
+
const progress = sampleIndex / 7;
|
|
17464
|
+
const latitude = -0.46 + progress * 0.74;
|
|
17465
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
|
|
17466
|
+
const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
17467
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
17468
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
17469
|
+
}
|
|
17470
|
+
}
|
|
17471
|
+
if (projectedPoints.length < 3) {
|
|
17472
|
+
continue;
|
|
17473
|
+
}
|
|
17474
|
+
context.beginPath();
|
|
17475
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
17476
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
17477
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
17478
|
+
}
|
|
17479
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
17480
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
17481
|
+
context.stroke();
|
|
17482
|
+
}
|
|
17483
|
+
context.restore();
|
|
17484
|
+
}
|
|
17485
|
+
/**
|
|
17486
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
17487
|
+
*
|
|
17488
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17489
|
+
*/
|
|
17490
|
+
function drawProjectedTentacleSuckers(options) {
|
|
17491
|
+
const { surfaceOptions, size } = options;
|
|
17492
|
+
const { timeMs } = surfaceOptions;
|
|
17493
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
17494
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
|
|
17495
|
+
continue;
|
|
17496
|
+
}
|
|
17497
|
+
for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
|
|
17498
|
+
const latitude = 0.52 + suckerIndex * 0.14;
|
|
17499
|
+
const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
|
|
17500
|
+
const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
|
|
17501
|
+
drawProjectedSurfaceSpot({
|
|
17502
|
+
...options,
|
|
17503
|
+
latitude,
|
|
17504
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
17505
|
+
radiusScale: size * (0.0065 - suckerIndex * 0.0007),
|
|
17506
|
+
});
|
|
17507
|
+
}
|
|
17508
|
+
}
|
|
17509
|
+
}
|
|
17510
|
+
/**
|
|
17511
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
17512
|
+
*
|
|
17513
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17514
|
+
*/
|
|
17515
|
+
function drawProjectedSurfaceSpot(options) {
|
|
17516
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
17517
|
+
const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
|
|
17518
|
+
const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
17519
|
+
const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
17520
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17521
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
17522
|
+
return;
|
|
17523
|
+
}
|
|
17524
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17525
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17526
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17527
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
17528
|
+
radiusScale *
|
|
17529
|
+
0.74, size * 0.003, size * 0.018);
|
|
17530
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
17531
|
+
radiusScale *
|
|
17532
|
+
0.52, size * 0.0024, size * 0.014);
|
|
17533
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
17534
|
+
context.save();
|
|
17535
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
17536
|
+
context.rotate(rotation);
|
|
17537
|
+
context.beginPath();
|
|
17538
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
17539
|
+
context.fillStyle = `${palette.highlight}73`;
|
|
17540
|
+
context.fill();
|
|
17541
|
+
context.strokeStyle = `${palette.highlight}99`;
|
|
17542
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
17543
|
+
context.stroke();
|
|
17544
|
+
context.restore();
|
|
17545
|
+
}
|
|
17546
|
+
/**
|
|
17547
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
17548
|
+
*
|
|
17549
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17550
|
+
*/
|
|
17551
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
17552
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
17553
|
+
}
|
|
17554
|
+
/**
|
|
17555
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
17556
|
+
*
|
|
17557
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17558
|
+
*/
|
|
17559
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
17560
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
17561
|
+
return progress * progress * (3 - 2 * progress);
|
|
17562
|
+
}
|
|
17563
|
+
|
|
17564
|
+
/* eslint-disable no-magic-numbers */
|
|
17565
|
+
/**
|
|
17566
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
17567
|
+
*
|
|
17568
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17569
|
+
*/
|
|
17570
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
17571
|
+
x: 0.32,
|
|
17572
|
+
y: -0.66,
|
|
17573
|
+
z: 1,
|
|
17574
|
+
});
|
|
17575
|
+
/**
|
|
17576
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
17577
|
+
*
|
|
17578
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17579
|
+
*/
|
|
17580
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
17581
|
+
x: -0.7,
|
|
17582
|
+
y: -0.18,
|
|
17583
|
+
z: 0.55,
|
|
17584
|
+
});
|
|
17585
|
+
/**
|
|
17586
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
17587
|
+
*
|
|
17588
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17589
|
+
*/
|
|
17590
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
17591
|
+
/**
|
|
17592
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
17593
|
+
*
|
|
17594
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17595
|
+
*/
|
|
17596
|
+
const SKIN_SPOT_COUNT = 14;
|
|
17597
|
+
/**
|
|
17598
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
17599
|
+
*
|
|
17600
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17601
|
+
*/
|
|
17602
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
17603
|
+
/**
|
|
17604
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
17605
|
+
*
|
|
17606
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17607
|
+
*/
|
|
17608
|
+
function getOctopus3d4StableState(createRandom) {
|
|
17609
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
17610
|
+
if (cached !== undefined) {
|
|
17611
|
+
return cached;
|
|
17612
|
+
}
|
|
17613
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
17614
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
17615
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
17616
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
17617
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
17618
|
+
const state = {
|
|
17619
|
+
morphologyProfile,
|
|
17620
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
17621
|
+
leftEyePhaseOffset,
|
|
17622
|
+
rightEyePhaseOffset,
|
|
17623
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
17624
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
17625
|
+
};
|
|
17626
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
17627
|
+
return state;
|
|
17628
|
+
}
|
|
17629
|
+
/**
|
|
17630
|
+
* Octopus 3D 4 avatar visual.
|
|
17631
|
+
*
|
|
17632
|
+
* @private built-in avatar visual
|
|
17633
|
+
*/
|
|
17634
|
+
const octopus3d4AvatarVisual = {
|
|
17635
|
+
id: 'octopus3d4',
|
|
17636
|
+
title: 'Octopus 3D 4',
|
|
17637
|
+
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.',
|
|
17638
|
+
isAnimated: true,
|
|
17639
|
+
supportsPointerTracking: true,
|
|
17640
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
17641
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
17642
|
+
const sceneCenterX = size * 0.5;
|
|
17643
|
+
const sceneCenterY = size * 0.535;
|
|
17644
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
17645
|
+
const meshCenter = {
|
|
17646
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
17647
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
17648
|
+
z: interaction.intensity * size * 0.02,
|
|
17649
|
+
};
|
|
17650
|
+
const rotationY = -0.08 +
|
|
17651
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
17652
|
+
interaction.bodyOffsetX * 0.24 +
|
|
17653
|
+
interaction.gazeX * 0.98;
|
|
17654
|
+
const rotationX = -0.07 +
|
|
17655
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
17656
|
+
interaction.bodyOffsetY * 0.08 -
|
|
17657
|
+
interaction.gazeY * 0.42;
|
|
17658
|
+
const surfaceOptions = {
|
|
17659
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
17660
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
17661
|
+
radiusZ: size *
|
|
17662
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
17663
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
17664
|
+
morphologyProfile,
|
|
17665
|
+
timeMs,
|
|
17666
|
+
animationPhase,
|
|
17667
|
+
tentacleProfiles,
|
|
17668
|
+
};
|
|
17669
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
17670
|
+
...surfaceOptions,
|
|
17671
|
+
center: meshCenter,
|
|
17672
|
+
rotationX,
|
|
17673
|
+
rotationY,
|
|
17674
|
+
sceneCenterX,
|
|
17675
|
+
sceneCenterY,
|
|
17676
|
+
size,
|
|
17677
|
+
palette,
|
|
17678
|
+
});
|
|
17679
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
17680
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
17681
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
17682
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
17683
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
17684
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
17685
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
17686
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
17687
|
+
drawAvatarFrame(context, size, palette);
|
|
17688
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
17689
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
17690
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
17691
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
17692
|
+
}
|
|
17693
|
+
drawBlobbySkinSpots({
|
|
17694
|
+
context,
|
|
17695
|
+
surfaceOptions,
|
|
17696
|
+
center: meshCenter,
|
|
17697
|
+
rotationX,
|
|
17698
|
+
rotationY,
|
|
17699
|
+
sceneCenterX,
|
|
17700
|
+
sceneCenterY,
|
|
17701
|
+
size,
|
|
17702
|
+
palette,
|
|
17703
|
+
skinSpots,
|
|
17704
|
+
});
|
|
17705
|
+
drawBlobbyContinuousCurrents({
|
|
17706
|
+
context,
|
|
17707
|
+
surfaceOptions,
|
|
17708
|
+
center: meshCenter,
|
|
17709
|
+
rotationX,
|
|
17710
|
+
rotationY,
|
|
17711
|
+
sceneCenterX,
|
|
17712
|
+
sceneCenterY,
|
|
17713
|
+
size,
|
|
17714
|
+
palette,
|
|
17715
|
+
morphologyProfile,
|
|
17716
|
+
timeMs,
|
|
17717
|
+
animationPhase,
|
|
17718
|
+
});
|
|
17719
|
+
drawBlobbyContinuousSuckers({
|
|
17720
|
+
context,
|
|
17721
|
+
surfaceOptions,
|
|
17722
|
+
center: meshCenter,
|
|
17723
|
+
rotationX,
|
|
17724
|
+
rotationY,
|
|
17725
|
+
sceneCenterX,
|
|
17726
|
+
sceneCenterY,
|
|
17727
|
+
size,
|
|
17728
|
+
palette,
|
|
17729
|
+
});
|
|
17730
|
+
drawBlobbyContinuousGloss({
|
|
17731
|
+
context,
|
|
17732
|
+
surfaceOptions,
|
|
17733
|
+
center: meshCenter,
|
|
17734
|
+
rotationX,
|
|
17735
|
+
rotationY,
|
|
17736
|
+
sceneCenterX,
|
|
17737
|
+
sceneCenterY,
|
|
17738
|
+
size,
|
|
17739
|
+
palette,
|
|
17740
|
+
});
|
|
17741
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17742
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17743
|
+
drawProjectedOrganicMouth(context, [
|
|
17744
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
17745
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
17746
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
17747
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
17748
|
+
},
|
|
17749
|
+
};
|
|
17750
|
+
/**
|
|
17751
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
17752
|
+
*
|
|
17753
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17754
|
+
*/
|
|
17755
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17756
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
17757
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
17758
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
17759
|
+
return {
|
|
17760
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
17761
|
+
progress * Math.PI * 1.8 +
|
|
17762
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
17763
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
17764
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
17765
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
17766
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
17767
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
17768
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17769
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17770
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
17771
|
+
};
|
|
17772
|
+
});
|
|
17773
|
+
}
|
|
17774
|
+
/**
|
|
17775
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
17776
|
+
*
|
|
17777
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17778
|
+
*/
|
|
17779
|
+
function createBlobbySkinSpots(createRandom) {
|
|
17780
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
17781
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
17782
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
17783
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
17784
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
17785
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
17786
|
+
}));
|
|
17787
|
+
}
|
|
17788
|
+
/**
|
|
17789
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
17790
|
+
*
|
|
17791
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17792
|
+
*/
|
|
17793
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
17794
|
+
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));
|
|
17795
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
17796
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
17797
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
17798
|
+
context.fillStyle = glowGradient;
|
|
17799
|
+
context.fillRect(0, 0, size, size);
|
|
17800
|
+
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);
|
|
17801
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
17802
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
17803
|
+
context.fillStyle = lowerGradient;
|
|
17804
|
+
context.fillRect(0, 0, size, size);
|
|
17805
|
+
}
|
|
17806
|
+
/**
|
|
17807
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
17808
|
+
*
|
|
17809
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17810
|
+
*/
|
|
17811
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
17812
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
17813
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
17814
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
17815
|
+
const ry = size * 0.062;
|
|
17816
|
+
context.save();
|
|
17817
|
+
context.translate(cx, cy);
|
|
17818
|
+
context.scale(1, ry / rx);
|
|
17819
|
+
const blurRadius = rx * 1.42;
|
|
17820
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
17821
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
17822
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
17823
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
17824
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
17825
|
+
context.fillStyle = shadowGradient;
|
|
17826
|
+
context.beginPath();
|
|
17827
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
17828
|
+
context.fill();
|
|
17829
|
+
context.restore();
|
|
17830
|
+
}
|
|
17831
|
+
/**
|
|
17832
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17833
|
+
*
|
|
17834
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17835
|
+
*/
|
|
17836
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
17837
|
+
/**
|
|
17838
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17839
|
+
*
|
|
17840
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17841
|
+
*/
|
|
17842
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
17843
|
+
/**
|
|
17844
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
17845
|
+
*
|
|
17846
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
17847
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
17848
|
+
* than re-evaluated for every patch corner.
|
|
17849
|
+
*
|
|
17850
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17851
|
+
*/
|
|
17852
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
17853
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17854
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
17855
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
17856
|
+
const surfacePatches = [];
|
|
17857
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17858
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
17859
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
17860
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
17861
|
+
}
|
|
17862
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17863
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
17864
|
+
}
|
|
17865
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
17866
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
17867
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
17868
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17869
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17870
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17871
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
17872
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
17873
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
17874
|
+
}
|
|
17875
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17876
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
17877
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
17878
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
17879
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
17880
|
+
}
|
|
17881
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
17882
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
17883
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
17884
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
17885
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
17886
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
17887
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
17888
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
17889
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
17890
|
+
}
|
|
17891
|
+
}
|
|
17892
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
17893
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
17894
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
17895
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
17896
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
17897
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
17898
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
17899
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17900
|
+
const transformedCorners = [
|
|
17901
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
17902
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
17903
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
17904
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
17905
|
+
];
|
|
17906
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
17907
|
+
if (surfaceNormal.z <= 0.006) {
|
|
17908
|
+
continue;
|
|
17909
|
+
}
|
|
17910
|
+
const projectedCorners = [
|
|
17911
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
17912
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
17913
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
17914
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
17915
|
+
];
|
|
17916
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
17917
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
17918
|
+
surfacePatches.push({
|
|
17919
|
+
corners: projectedCorners,
|
|
17920
|
+
averageDepth: (transformedCorners[0].z +
|
|
17921
|
+
transformedCorners[1].z +
|
|
17922
|
+
transformedCorners[2].z +
|
|
17923
|
+
transformedCorners[3].z) /
|
|
17924
|
+
4,
|
|
17925
|
+
lightIntensity,
|
|
17926
|
+
rimLightIntensity,
|
|
17927
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17928
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
17929
|
+
});
|
|
17930
|
+
}
|
|
17931
|
+
}
|
|
17932
|
+
return surfacePatches;
|
|
17933
|
+
}
|
|
17934
|
+
/**
|
|
17935
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
17936
|
+
*
|
|
17937
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17938
|
+
*/
|
|
17939
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
17940
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
17941
|
+
}
|
|
17942
|
+
/**
|
|
17943
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
17944
|
+
*
|
|
17945
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
17946
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
17947
|
+
* of `latitudePatchCount * 4` times.
|
|
17948
|
+
*
|
|
17949
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17950
|
+
*/
|
|
17951
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
17952
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
17953
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17954
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17955
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
17956
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
17957
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
17958
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
17959
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
17960
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17961
|
+
animationPhase * 0.6 +
|
|
17962
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
17963
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
17964
|
+
(0.32 + lowerBlend * 0.7);
|
|
17965
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
17966
|
+
lowerBlend *
|
|
17967
|
+
tentacleInfluence.core *
|
|
17968
|
+
tentacleInfluence.swayScale;
|
|
17969
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
17970
|
+
lowerBlend *
|
|
17971
|
+
tentacleInfluence.core *
|
|
17972
|
+
tentacleInfluence.swayScale *
|
|
17973
|
+
0.42;
|
|
17974
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
17975
|
+
tipBlend *
|
|
17976
|
+
tentacleInfluence.core *
|
|
17977
|
+
tentacleInfluence.curlScale *
|
|
17978
|
+
0.36;
|
|
17979
|
+
const horizontalScale = 1.06 +
|
|
17980
|
+
mantleRipple +
|
|
17981
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
17982
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
17983
|
+
upperBlend * 0.08;
|
|
17984
|
+
const depthScale = 1.08 +
|
|
17985
|
+
upperBlend * 0.16 +
|
|
17986
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
17987
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
17988
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
17989
|
+
const tentacleTubeRadius = lowerBlend *
|
|
17990
|
+
tentacleInfluence.core *
|
|
17991
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
17992
|
+
radiusX;
|
|
17993
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
17994
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
17995
|
+
const lowerDrop = lowerBlend *
|
|
17996
|
+
radiusY *
|
|
17997
|
+
(0.2 +
|
|
17998
|
+
tentacleInfluence.core *
|
|
17999
|
+
(0.42 +
|
|
18000
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
18001
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
18002
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
18003
|
+
return {
|
|
18004
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
18005
|
+
combinedTentacleSway +
|
|
18006
|
+
tentacleCurl * radiusX * 0.18,
|
|
18007
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
18008
|
+
upperBlend * radiusY * 0.12 +
|
|
18009
|
+
lowerDrop +
|
|
18010
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
18011
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
18012
|
+
lowerBlend *
|
|
18013
|
+
tipBlend *
|
|
18014
|
+
tentacleInfluence.core *
|
|
18015
|
+
radiusY *
|
|
18016
|
+
0.04,
|
|
18017
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
18018
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
18019
|
+
lowerBlend *
|
|
18020
|
+
tentacleInfluence.core *
|
|
18021
|
+
radiusZ *
|
|
18022
|
+
0.044 +
|
|
18023
|
+
tentacleCurl * radiusZ * 0.14,
|
|
18024
|
+
};
|
|
18025
|
+
}
|
|
18026
|
+
/**
|
|
18027
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
18028
|
+
*
|
|
18029
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18030
|
+
*/
|
|
18031
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
18032
|
+
let totalWeight = 0;
|
|
18033
|
+
let weightedSin = 0;
|
|
18034
|
+
let weightedCos = 0;
|
|
18035
|
+
let weightedWidthScale = 0;
|
|
18036
|
+
let weightedLengthScale = 0;
|
|
18037
|
+
let weightedSwayScale = 0;
|
|
18038
|
+
let weightedDepthScale = 0;
|
|
18039
|
+
let weightedCurlScale = 0;
|
|
18040
|
+
let weightedPrimaryPhase = 0;
|
|
18041
|
+
let weightedSecondaryPhase = 0;
|
|
18042
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
18043
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
18044
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
18045
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
18046
|
+
totalWeight += weight;
|
|
18047
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
18048
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
18049
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
18050
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
18051
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
18052
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
18053
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
18054
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
18055
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
18056
|
+
}
|
|
18057
|
+
if (totalWeight < 0.0001) {
|
|
18058
|
+
return {
|
|
18059
|
+
core: 0,
|
|
18060
|
+
centerLongitude: longitude,
|
|
18061
|
+
widthScale: 1,
|
|
18062
|
+
lengthScale: 1,
|
|
18063
|
+
swayScale: 1,
|
|
18064
|
+
depthScale: 1,
|
|
18065
|
+
curlScale: 1,
|
|
18066
|
+
primaryPhase: 0,
|
|
18067
|
+
secondaryPhase: 0,
|
|
18068
|
+
};
|
|
18069
|
+
}
|
|
18070
|
+
return {
|
|
18071
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
18072
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
18073
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
18074
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
18075
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
18076
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
18077
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
18078
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
18079
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
18080
|
+
};
|
|
18081
|
+
}
|
|
18082
|
+
/**
|
|
18083
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
18084
|
+
*
|
|
18085
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18086
|
+
*/
|
|
18087
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
18088
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
18089
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
18090
|
+
animationPhase +
|
|
18091
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
18092
|
+
1) /
|
|
18093
|
+
2);
|
|
18094
|
+
}
|
|
18095
|
+
/**
|
|
18096
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
18097
|
+
*
|
|
18098
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18099
|
+
*/
|
|
18100
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
18101
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
18102
|
+
if (tonalProgress < 0.12) {
|
|
18103
|
+
return palette.highlight;
|
|
18104
|
+
}
|
|
18105
|
+
if (tonalProgress < 0.3) {
|
|
18106
|
+
return palette.secondary;
|
|
18107
|
+
}
|
|
18108
|
+
if (tonalProgress < 0.72) {
|
|
18109
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
18110
|
+
}
|
|
18111
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
18112
|
+
}
|
|
18113
|
+
/**
|
|
18114
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
18115
|
+
*
|
|
18116
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18117
|
+
*/
|
|
18118
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
18119
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
18120
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
18121
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
18122
|
+
}
|
|
18123
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
18124
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
18125
|
+
}
|
|
18126
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
18127
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
18128
|
+
}
|
|
18129
|
+
context.save();
|
|
18130
|
+
context.beginPath();
|
|
18131
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
18132
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
18133
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
18134
|
+
}
|
|
18135
|
+
context.closePath();
|
|
18136
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
18137
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
18138
|
+
context.lineJoin = 'round';
|
|
18139
|
+
context.stroke();
|
|
18140
|
+
context.restore();
|
|
18141
|
+
}
|
|
18142
|
+
/**
|
|
18143
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
18144
|
+
*
|
|
18145
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18146
|
+
*/
|
|
18147
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
18148
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
18149
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
18150
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
18151
|
+
context.save();
|
|
18152
|
+
context.lineCap = 'round';
|
|
18153
|
+
context.lineJoin = 'round';
|
|
18154
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
18155
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
18156
|
+
const projectedPoints = [];
|
|
18157
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
18158
|
+
const progress = sampleIndex / 8;
|
|
18159
|
+
const latitude = -0.48 + progress * 0.78;
|
|
18160
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
18161
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
18162
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
18163
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
18164
|
+
}
|
|
18165
|
+
}
|
|
18166
|
+
if (projectedPoints.length < 3) {
|
|
18167
|
+
continue;
|
|
18168
|
+
}
|
|
18169
|
+
context.beginPath();
|
|
18170
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
18171
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
18172
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
18173
|
+
}
|
|
18174
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
18175
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
18176
|
+
context.stroke();
|
|
18177
|
+
}
|
|
18178
|
+
context.restore();
|
|
18179
|
+
}
|
|
18180
|
+
/**
|
|
18181
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
18182
|
+
*
|
|
18183
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18184
|
+
*/
|
|
18185
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
18186
|
+
const { surfaceOptions, size } = options;
|
|
18187
|
+
const { timeMs } = surfaceOptions;
|
|
18188
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
18189
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
18190
|
+
continue;
|
|
18191
|
+
}
|
|
18192
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
18193
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
18194
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
18195
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
18196
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
18197
|
+
...options,
|
|
18198
|
+
latitude,
|
|
18199
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
18200
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
18201
|
+
});
|
|
18202
|
+
}
|
|
18203
|
+
}
|
|
18204
|
+
}
|
|
18205
|
+
/**
|
|
18206
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
18207
|
+
*
|
|
18208
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18209
|
+
*/
|
|
18210
|
+
function drawBlobbySkinSpots(options) {
|
|
18211
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
18212
|
+
for (const skinSpot of skinSpots) {
|
|
18213
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
18214
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18215
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
18216
|
+
continue;
|
|
18217
|
+
}
|
|
18218
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18219
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
18220
|
+
context.save();
|
|
18221
|
+
context.beginPath();
|
|
18222
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
18223
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
18224
|
+
context.fill();
|
|
18225
|
+
context.restore();
|
|
18226
|
+
}
|
|
18227
|
+
}
|
|
18228
|
+
/**
|
|
18229
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
18230
|
+
*
|
|
18231
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18232
|
+
*/
|
|
18233
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
18234
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
18235
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
18236
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
18237
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
18238
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18239
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
18240
|
+
return;
|
|
18241
|
+
}
|
|
18242
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18243
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18244
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18245
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
18246
|
+
radiusScale *
|
|
18247
|
+
0.78, size * 0.003, size * 0.02);
|
|
18248
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
18249
|
+
radiusScale *
|
|
18250
|
+
0.54, size * 0.0024, size * 0.015);
|
|
18251
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
18252
|
+
context.save();
|
|
18253
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
18254
|
+
context.rotate(rotation);
|
|
18255
|
+
context.beginPath();
|
|
18256
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
18257
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
18258
|
+
context.fill();
|
|
18259
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
18260
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
18261
|
+
context.stroke();
|
|
18262
|
+
context.restore();
|
|
18263
|
+
}
|
|
18264
|
+
/**
|
|
18265
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
18266
|
+
*
|
|
18267
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18268
|
+
*/
|
|
18269
|
+
function drawBlobbyContinuousGloss(options) {
|
|
18270
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
18271
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
18272
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
18273
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
18274
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
18275
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18276
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
18277
|
+
return;
|
|
18278
|
+
}
|
|
18279
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18280
|
+
const glossRadius = size * 0.058;
|
|
18281
|
+
context.save();
|
|
18282
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
18283
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
18284
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
18285
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
18286
|
+
context.fillStyle = glossGradient;
|
|
18287
|
+
context.beginPath();
|
|
18288
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
18289
|
+
context.fill();
|
|
18290
|
+
context.restore();
|
|
18291
|
+
}
|
|
18292
|
+
/**
|
|
18293
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
18294
|
+
*
|
|
18295
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18296
|
+
*/
|
|
18297
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
18298
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
18299
|
+
}
|
|
18300
|
+
/**
|
|
18301
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
18302
|
+
*
|
|
18303
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18304
|
+
*/
|
|
18305
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
18306
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
18307
|
+
return progress * progress * (3 - 2 * progress);
|
|
18308
|
+
}
|
|
18309
|
+
/**
|
|
18310
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
18311
|
+
*
|
|
18312
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18313
|
+
*/
|
|
18314
|
+
function formatAlphaHex(opacity) {
|
|
18315
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
18316
|
+
.toString(16)
|
|
18317
|
+
.padStart(2, '0');
|
|
18318
|
+
}
|
|
18319
|
+
|
|
16770
18320
|
/* eslint-disable no-magic-numbers */
|
|
16771
18321
|
/**
|
|
16772
18322
|
* Octopus avatar visual.
|
|
@@ -17537,6 +19087,8 @@ const AVATAR_VISUALS = [
|
|
|
17537
19087
|
octopus3AvatarVisual,
|
|
17538
19088
|
octopus3dAvatarVisual,
|
|
17539
19089
|
octopus3d2AvatarVisual,
|
|
19090
|
+
octopus3d3AvatarVisual,
|
|
19091
|
+
octopus3d4AvatarVisual,
|
|
17540
19092
|
asciiOctopusAvatarVisual,
|
|
17541
19093
|
minecraftAvatarVisual,
|
|
17542
19094
|
minecraft2AvatarVisual,
|
|
@@ -19825,7 +21377,15 @@ function buildTeammateMetadata(entry) {
|
|
|
19825
21377
|
* Builds the teammate request text, optionally including context.
|
|
19826
21378
|
*/
|
|
19827
21379
|
function buildTeammateRequest(message, context) {
|
|
19828
|
-
|
|
21380
|
+
if (!context) {
|
|
21381
|
+
return message;
|
|
21382
|
+
}
|
|
21383
|
+
return spaceTrim$1((block) => `
|
|
21384
|
+
${block(message)}
|
|
21385
|
+
|
|
21386
|
+
Context:
|
|
21387
|
+
${block(context)}
|
|
21388
|
+
`);
|
|
19829
21389
|
}
|
|
19830
21390
|
/**
|
|
19831
21391
|
* Builds a minimal chat prompt for teammate calls.
|
|
@@ -23814,7 +25374,11 @@ function createUseProjectToolFunctions() {
|
|
|
23814
25374
|
const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
|
|
23815
25375
|
const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
|
|
23816
25376
|
const contentToReturn = wasCharacterTruncated
|
|
23817
|
-
?
|
|
25377
|
+
? spaceTrim$1((block) => `
|
|
25378
|
+
${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
|
|
25379
|
+
|
|
25380
|
+
[...truncated...]
|
|
25381
|
+
`)
|
|
23818
25382
|
: lineRangedContent;
|
|
23819
25383
|
const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
|
|
23820
25384
|
if (wasCharacterTruncated) {
|
|
@@ -32749,11 +34313,19 @@ const JsonFileImportPlugin = {
|
|
|
32749
34313
|
try {
|
|
32750
34314
|
const json = JSON.parse(content);
|
|
32751
34315
|
const formattedJson = JSON.stringify(json, null, 4);
|
|
32752
|
-
return
|
|
34316
|
+
return spaceTrim$1((block) => `
|
|
34317
|
+
\`\`\`json
|
|
34318
|
+
${block(formattedJson)}
|
|
34319
|
+
\`\`\`
|
|
34320
|
+
`);
|
|
32753
34321
|
}
|
|
32754
34322
|
catch (error) {
|
|
32755
34323
|
// If JSON is invalid, still import it but maybe not as pretty JSON
|
|
32756
|
-
return
|
|
34324
|
+
return spaceTrim$1((block) => `
|
|
34325
|
+
\`\`\`json
|
|
34326
|
+
${block(content)}
|
|
34327
|
+
\`\`\`
|
|
34328
|
+
`);
|
|
32757
34329
|
}
|
|
32758
34330
|
},
|
|
32759
34331
|
};
|
|
@@ -32777,7 +34349,11 @@ const TextFileImportPlugin = {
|
|
|
32777
34349
|
import(content, mimeType) {
|
|
32778
34350
|
const extension = mimeTypeToExtension(mimeType);
|
|
32779
34351
|
const codeBlockType = extension || 'txt';
|
|
32780
|
-
return
|
|
34352
|
+
return spaceTrim$1((block) => `
|
|
34353
|
+
\`\`\`${codeBlockType}
|
|
34354
|
+
${block(content)}
|
|
34355
|
+
\`\`\`
|
|
34356
|
+
`);
|
|
32781
34357
|
},
|
|
32782
34358
|
};
|
|
32783
34359
|
|
|
@@ -33024,11 +34600,19 @@ function collectExampleInteractionLines(parseResult, samples) {
|
|
|
33024
34600
|
const examples = [];
|
|
33025
34601
|
const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
|
|
33026
34602
|
if (initialMessage) {
|
|
33027
|
-
examples.push(
|
|
34603
|
+
examples.push(spaceTrim$1((block) => `
|
|
34604
|
+
**Agent:**
|
|
34605
|
+
${block(initialMessage)}
|
|
34606
|
+
`));
|
|
33028
34607
|
}
|
|
33029
34608
|
if (samples && samples.length > 0) {
|
|
33030
34609
|
for (const sample of samples) {
|
|
33031
|
-
examples.push(
|
|
34610
|
+
examples.push(spaceTrim$1((block) => `
|
|
34611
|
+
**User:** ${block(String(sample.question))}
|
|
34612
|
+
|
|
34613
|
+
**Agent:**
|
|
34614
|
+
${block(sample.answer)}
|
|
34615
|
+
`));
|
|
33032
34616
|
}
|
|
33033
34617
|
}
|
|
33034
34618
|
return examples;
|
|
@@ -34350,7 +35934,9 @@ class OpenAiAgentKitExecutionToolsToolBuilder {
|
|
|
34350
35934
|
if (!executionTools || !executionTools.script) {
|
|
34351
35935
|
throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
|
|
34352
35936
|
}
|
|
34353
|
-
return Array.isArray(executionTools.script)
|
|
35937
|
+
return Array.isArray(executionTools.script)
|
|
35938
|
+
? executionTools.script
|
|
35939
|
+
: [executionTools.script];
|
|
34354
35940
|
}
|
|
34355
35941
|
/**
|
|
34356
35942
|
* Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
|
|
@@ -37412,7 +38998,7 @@ class OpenAiVectorStoreKnowledgeSourcePreparer {
|
|
|
37412
38998
|
return { skippedReason: 'invalid_data_url' };
|
|
37413
38999
|
}
|
|
37414
39000
|
return {
|
|
37415
|
-
file: new File([parsed.buffer], parsed.filename, {
|
|
39001
|
+
file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
|
|
37416
39002
|
type: parsed.mimeType,
|
|
37417
39003
|
}),
|
|
37418
39004
|
sizeBytes: parsed.buffer.length,
|
|
@@ -39160,7 +40746,7 @@ function emitAgentLlmExecutionToolsAssistantPreparationProgress(options) {
|
|
|
39160
40746
|
* Computes one stable hash from a JSON-serializable value.
|
|
39161
40747
|
*/
|
|
39162
40748
|
function computeJsonHash$1(value) {
|
|
39163
|
-
return SHA256(JSON.stringify(value)).toString();
|
|
40749
|
+
return CryptoJS.SHA256(JSON.stringify(value)).toString();
|
|
39164
40750
|
}
|
|
39165
40751
|
/**
|
|
39166
40752
|
* Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
|
|
@@ -39318,7 +40904,7 @@ AgentLlmExecutionToolsAgentKitRunner.vectorStoreCache = new Map();
|
|
|
39318
40904
|
* Computes one stable hash from a JSON-serializable value.
|
|
39319
40905
|
*/
|
|
39320
40906
|
function computeJsonHash(value) {
|
|
39321
|
-
return SHA256(JSON.stringify(value)).toString();
|
|
40907
|
+
return CryptoJS.SHA256(JSON.stringify(value)).toString();
|
|
39322
40908
|
}
|
|
39323
40909
|
/**
|
|
39324
40910
|
* Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
|
|
@@ -39654,7 +41240,11 @@ function truncateAttachmentInlineText(content, maxCharacters) {
|
|
|
39654
41240
|
};
|
|
39655
41241
|
}
|
|
39656
41242
|
return {
|
|
39657
|
-
content:
|
|
41243
|
+
content: spaceTrim$1((block) => `
|
|
41244
|
+
${block(content.slice(0, Math.max(0, maxCharacters)))}
|
|
41245
|
+
|
|
41246
|
+
[...truncated...]
|
|
41247
|
+
`),
|
|
39658
41248
|
isTruncated: true,
|
|
39659
41249
|
};
|
|
39660
41250
|
}
|
|
@@ -40120,7 +41710,7 @@ class AgentLlmExecutionTools {
|
|
|
40120
41710
|
* Returns a virtual model name representing the agent behavior.
|
|
40121
41711
|
*/
|
|
40122
41712
|
get modelName() {
|
|
40123
|
-
const hash = SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
|
|
41713
|
+
const hash = CryptoJS.SHA256(hexEncoder.parse(this.options.agentSource)).toString( /* hex */);
|
|
40124
41714
|
const agentId = hash.substring(0, 10);
|
|
40125
41715
|
return (normalizeToKebabCase(this.title) + '-' + agentId);
|
|
40126
41716
|
}
|