@promptbook/core 0.112.0-99 → 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 +1767 -128
- package/esm/index.es.js.map +1 -1
- package/esm/servers.d.ts +1 -9
- 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 +1 -1
- package/umd/index.umd.js +1769 -129
- package/umd/index.umd.js.map +1 -1
- package/umd/servers.d.ts +1 -9
- 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/umd/index.umd.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('crypto-js'), require('crypto-js/enc-hex'), require('spacetrim'), require('
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'crypto-js', 'crypto-js/enc-hex', 'spacetrim', '
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-core"] = {}, global.
|
|
5
|
-
})(this, (function (exports,
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('crypto-js'), require('crypto-js/enc-hex'), require('spacetrim'), require('rxjs'), require('waitasecond'), require('crypto-js/sha256'), require('path'), require('mime-types'), require('papaparse'), require('moment'), require('crypto'), require('colors'), require('@openai/agents'), require('bottleneck'), require('openai')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'crypto-js', 'crypto-js/enc-hex', 'spacetrim', 'rxjs', 'waitasecond', 'crypto-js/sha256', 'path', 'mime-types', 'papaparse', 'moment', 'crypto', 'colors', '@openai/agents', 'bottleneck', 'openai'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-core"] = {}, global.CryptoJS, global.hexEncoder, global.spacetrim, global.rxjs, global.waitasecond, global.sha256, global.path, global.mimeTypes, global.papaparse, global.moment, global.crypto, global.colors, global.agents, global.Bottleneck, global.OpenAI));
|
|
5
|
+
})(this, (function (exports, CryptoJS, hexEncoder, spacetrim, rxjs, waitasecond, sha256, path, mimeTypes, papaparse, moment, crypto, colors, agents, Bottleneck, OpenAI) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
8
|
|
|
9
|
+
var CryptoJS__default = /*#__PURE__*/_interopDefaultLegacy(CryptoJS);
|
|
9
10
|
var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
|
|
10
11
|
var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
|
|
12
|
+
var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
|
|
11
13
|
var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
|
|
12
14
|
var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
|
|
13
15
|
var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
|
|
@@ -27,7 +29,7 @@
|
|
|
27
29
|
* @generated
|
|
28
30
|
* @see https://github.com/webgptorg/promptbook
|
|
29
31
|
*/
|
|
30
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0
|
|
32
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0';
|
|
31
33
|
/**
|
|
32
34
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
33
35
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1652,7 +1654,7 @@
|
|
|
1652
1654
|
* @public exported from `@promptbook/utils`
|
|
1653
1655
|
*/
|
|
1654
1656
|
function computeHash(value) {
|
|
1655
|
-
return
|
|
1657
|
+
return CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(spacetrim.spaceTrim(valueToString(value)))).toString( /* hex */);
|
|
1656
1658
|
}
|
|
1657
1659
|
// TODO: [🥬][🥬] Use this ACRY
|
|
1658
1660
|
|
|
@@ -3772,7 +3774,7 @@
|
|
|
3772
3774
|
* @private internal helper function
|
|
3773
3775
|
*/
|
|
3774
3776
|
function $randomToken(randomness) {
|
|
3775
|
-
return
|
|
3777
|
+
return CryptoJS__default["default"].lib.WordArray.random(randomness).toString(CryptoJS__default["default"].enc.Hex);
|
|
3776
3778
|
}
|
|
3777
3779
|
// TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
|
|
3778
3780
|
// TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
@@ -5456,7 +5458,7 @@
|
|
|
5456
5458
|
* @public exported from `@promptbook/editable`
|
|
5457
5459
|
*/
|
|
5458
5460
|
function knowledgeSourceContentToName(knowledgeSourceContent) {
|
|
5459
|
-
const hash =
|
|
5461
|
+
const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
|
|
5460
5462
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
5461
5463
|
.toString( /* hex */)
|
|
5462
5464
|
.substring(0, 20);
|
|
@@ -6369,7 +6371,7 @@
|
|
|
6369
6371
|
console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
|
|
6370
6372
|
value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
6371
6373
|
}
|
|
6372
|
-
const csv =
|
|
6374
|
+
const csv = papaparse__default["default"].parse(value, settings);
|
|
6373
6375
|
return csv;
|
|
6374
6376
|
}
|
|
6375
6377
|
|
|
@@ -6454,10 +6456,10 @@
|
|
|
6454
6456
|
i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
|
|
6455
6457
|
);
|
|
6456
6458
|
*/
|
|
6457
|
-
await onProgress(
|
|
6459
|
+
await onProgress(papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
|
|
6458
6460
|
}
|
|
6459
6461
|
}
|
|
6460
|
-
return
|
|
6462
|
+
return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6461
6463
|
},
|
|
6462
6464
|
},
|
|
6463
6465
|
{
|
|
@@ -6485,7 +6487,7 @@
|
|
|
6485
6487
|
return /* not await */ mapCallback({ [key]: value }, index, array.length);
|
|
6486
6488
|
}));
|
|
6487
6489
|
}));
|
|
6488
|
-
return
|
|
6490
|
+
return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6489
6491
|
},
|
|
6490
6492
|
},
|
|
6491
6493
|
],
|
|
@@ -9897,6 +9899,11 @@
|
|
|
9897
9899
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
9898
9900
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
9899
9901
|
*
|
|
9902
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
9903
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
9904
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
9905
|
+
* outbound request.
|
|
9906
|
+
*
|
|
9900
9907
|
* @public exported from `@promptbook/utils`
|
|
9901
9908
|
*/
|
|
9902
9909
|
function isValidAgentUrl(url) {
|
|
@@ -9910,12 +9917,6 @@
|
|
|
9910
9917
|
// TODO: [🐠]
|
|
9911
9918
|
return false;
|
|
9912
9919
|
}
|
|
9913
|
-
/*
|
|
9914
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
9915
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
9916
|
-
return false;
|
|
9917
|
-
}
|
|
9918
|
-
*/
|
|
9919
9920
|
return true;
|
|
9920
9921
|
}
|
|
9921
9922
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -10091,8 +10092,11 @@
|
|
|
10091
10092
|
if (!trimmedContent) {
|
|
10092
10093
|
return requirements;
|
|
10093
10094
|
}
|
|
10094
|
-
|
|
10095
|
-
|
|
10095
|
+
const goalSection = spacetrim.spaceTrim((block) => `
|
|
10096
|
+
## Goal
|
|
10097
|
+
|
|
10098
|
+
${block(trimmedContent)}
|
|
10099
|
+
`);
|
|
10096
10100
|
const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
|
|
10097
10101
|
return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
|
|
10098
10102
|
}
|
|
@@ -10614,8 +10618,11 @@
|
|
|
10614
10618
|
if (!trimmedContent) {
|
|
10615
10619
|
return requirements;
|
|
10616
10620
|
}
|
|
10617
|
-
|
|
10618
|
-
|
|
10621
|
+
const languageSection = spacetrim.spaceTrim((block) => `
|
|
10622
|
+
## Language
|
|
10623
|
+
|
|
10624
|
+
- Your language is ${block(trimmedContent)}
|
|
10625
|
+
`);
|
|
10619
10626
|
return this.appendToSystemMessage(requirements, languageSection, '\n\n');
|
|
10620
10627
|
}
|
|
10621
10628
|
}
|
|
@@ -13062,21 +13069,22 @@
|
|
|
13062
13069
|
* @private helper of `fractalAvatarVisual`
|
|
13063
13070
|
*/
|
|
13064
13071
|
function drawDragonCurveLayer(context, points, options) {
|
|
13065
|
-
const {
|
|
13072
|
+
const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
|
|
13066
13073
|
const firstPoint = points[0];
|
|
13067
13074
|
const lastPoint = points[points.length - 1];
|
|
13068
13075
|
const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
|
|
13069
13076
|
ribbonGradient.addColorStop(0, `${primaryColor}f2`);
|
|
13070
13077
|
ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
|
|
13071
13078
|
ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
|
|
13079
|
+
// Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
|
|
13080
|
+
// context.filter blur, which triggers a costly software rasterization pass every frame.
|
|
13072
13081
|
context.save();
|
|
13073
13082
|
context.beginPath();
|
|
13074
13083
|
tracePolyline(context, points);
|
|
13075
|
-
context.strokeStyle = `${shadowColor}
|
|
13076
|
-
context.lineWidth = strokeWidth *
|
|
13084
|
+
context.strokeStyle = `${shadowColor}48`;
|
|
13085
|
+
context.lineWidth = strokeWidth * 4.5;
|
|
13077
13086
|
context.lineJoin = 'round';
|
|
13078
13087
|
context.lineCap = 'round';
|
|
13079
|
-
context.filter = `blur(${size * 0.022}px)`;
|
|
13080
13088
|
context.stroke();
|
|
13081
13089
|
context.restore();
|
|
13082
13090
|
context.beginPath();
|
|
@@ -13538,7 +13546,7 @@
|
|
|
13538
13546
|
*
|
|
13539
13547
|
* @private helper of `minecraft2AvatarVisual`
|
|
13540
13548
|
*/
|
|
13541
|
-
const LIGHT_DIRECTION$
|
|
13549
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
13542
13550
|
x: 0.4,
|
|
13543
13551
|
y: -0.65,
|
|
13544
13552
|
z: 0.92,
|
|
@@ -13660,11 +13668,23 @@
|
|
|
13660
13668
|
* @private helper of `minecraft2AvatarVisual`
|
|
13661
13669
|
*/
|
|
13662
13670
|
function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
|
|
13671
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.03;
|
|
13672
|
+
const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
|
|
13673
|
+
const rx = size * (0.16 + interaction.intensity * 0.015);
|
|
13674
|
+
const ry = size * 0.055;
|
|
13675
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
13663
13676
|
context.save();
|
|
13664
|
-
context.
|
|
13665
|
-
context.
|
|
13677
|
+
context.translate(cx, cy);
|
|
13678
|
+
context.scale(1, ry / rx);
|
|
13679
|
+
const blurRadius = rx * 1.4;
|
|
13680
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
13681
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
13682
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
13683
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
13684
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
13685
|
+
context.fillStyle = shadowGradient;
|
|
13666
13686
|
context.beginPath();
|
|
13667
|
-
context.
|
|
13687
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
13668
13688
|
context.fill();
|
|
13669
13689
|
context.restore();
|
|
13670
13690
|
}
|
|
@@ -13750,7 +13770,7 @@
|
|
|
13750
13770
|
corners: projectedCorners,
|
|
13751
13771
|
texture: faceDefinition.texture,
|
|
13752
13772
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
13753
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
13773
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
13754
13774
|
outlineColor: cuboid.outlineColor,
|
|
13755
13775
|
};
|
|
13756
13776
|
});
|
|
@@ -13888,13 +13908,27 @@
|
|
|
13888
13908
|
spotlight.addColorStop(1, `${palette.highlight}00`);
|
|
13889
13909
|
context.fillStyle = spotlight;
|
|
13890
13910
|
context.fillRect(0, 0, size, size);
|
|
13891
|
-
|
|
13892
|
-
|
|
13893
|
-
|
|
13894
|
-
|
|
13895
|
-
|
|
13896
|
-
|
|
13897
|
-
|
|
13911
|
+
{
|
|
13912
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
13913
|
+
const cx = size * 0.5;
|
|
13914
|
+
const cy = size * 0.86;
|
|
13915
|
+
const rx = size * 0.2;
|
|
13916
|
+
const ry = size * 0.06;
|
|
13917
|
+
const blurRadius = rx * 1.4;
|
|
13918
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
13919
|
+
shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
|
|
13920
|
+
shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
|
|
13921
|
+
shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
|
|
13922
|
+
shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
13923
|
+
context.save();
|
|
13924
|
+
context.translate(cx, cy);
|
|
13925
|
+
context.scale(1, ry / rx);
|
|
13926
|
+
context.fillStyle = shadowGradient;
|
|
13927
|
+
context.beginPath();
|
|
13928
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
13929
|
+
context.fill();
|
|
13930
|
+
context.restore();
|
|
13931
|
+
}
|
|
13898
13932
|
drawVoxelCuboid(context, {
|
|
13899
13933
|
x: bodyX,
|
|
13900
13934
|
y: bodyY,
|
|
@@ -14423,7 +14457,7 @@
|
|
|
14423
14457
|
*
|
|
14424
14458
|
* @private helper of `octopus3AvatarVisual`
|
|
14425
14459
|
*/
|
|
14426
|
-
function formatAlphaHex$
|
|
14460
|
+
function formatAlphaHex$2(opacity) {
|
|
14427
14461
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
14428
14462
|
.toString(16)
|
|
14429
14463
|
.padStart(2, '0');
|
|
@@ -14802,7 +14836,7 @@
|
|
|
14802
14836
|
context.beginPath();
|
|
14803
14837
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
14804
14838
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
14805
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
14839
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
14806
14840
|
context.lineWidth = radiusX * 0.08;
|
|
14807
14841
|
context.lineCap = 'round';
|
|
14808
14842
|
context.stroke();
|
|
@@ -14883,7 +14917,7 @@
|
|
|
14883
14917
|
context.beginPath();
|
|
14884
14918
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
14885
14919
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
14886
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
14920
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
14887
14921
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
14888
14922
|
context.lineCap = 'round';
|
|
14889
14923
|
context.stroke();
|
|
@@ -14929,7 +14963,7 @@
|
|
|
14929
14963
|
*
|
|
14930
14964
|
* @private helper of the 3D octopus avatar visuals
|
|
14931
14965
|
*/
|
|
14932
|
-
function formatAlphaHex(opacity) {
|
|
14966
|
+
function formatAlphaHex$1(opacity) {
|
|
14933
14967
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
14934
14968
|
.toString(16)
|
|
14935
14969
|
.padStart(2, '0');
|
|
@@ -14941,11 +14975,40 @@
|
|
|
14941
14975
|
*
|
|
14942
14976
|
* @private helper of `octopus3dAvatarVisual`
|
|
14943
14977
|
*/
|
|
14944
|
-
const LIGHT_DIRECTION$
|
|
14978
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
14945
14979
|
x: 0.48,
|
|
14946
14980
|
y: -0.62,
|
|
14947
14981
|
z: 0.94,
|
|
14948
14982
|
});
|
|
14983
|
+
/**
|
|
14984
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
14985
|
+
*
|
|
14986
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
14987
|
+
*/
|
|
14988
|
+
const octopus3dStableStateCache = new WeakMap();
|
|
14989
|
+
/**
|
|
14990
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
14991
|
+
*
|
|
14992
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
14993
|
+
*/
|
|
14994
|
+
function getOctopus3dStableState(createRandom) {
|
|
14995
|
+
const cached = octopus3dStableStateCache.get(createRandom);
|
|
14996
|
+
if (cached !== undefined) {
|
|
14997
|
+
return cached;
|
|
14998
|
+
}
|
|
14999
|
+
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
15000
|
+
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
15001
|
+
const leftEyePhaseOffset = eyeRandom() * 0.6;
|
|
15002
|
+
const rightEyePhaseOffset = eyeRandom() * 0.6;
|
|
15003
|
+
const state = {
|
|
15004
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
15005
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
15006
|
+
leftEyePhaseOffset,
|
|
15007
|
+
rightEyePhaseOffset,
|
|
15008
|
+
};
|
|
15009
|
+
octopus3dStableStateCache.set(createRandom, state);
|
|
15010
|
+
return state;
|
|
15011
|
+
}
|
|
14949
15012
|
/**
|
|
14950
15013
|
* Proper 3D Octopus visual built from projected organic meshes and tentacles.
|
|
14951
15014
|
*
|
|
@@ -14958,10 +15021,7 @@
|
|
|
14958
15021
|
isAnimated: true,
|
|
14959
15022
|
supportsPointerTracking: true,
|
|
14960
15023
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
14961
|
-
const morphologyProfile =
|
|
14962
|
-
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
14963
|
-
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
14964
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
15024
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
|
|
14965
15025
|
const sceneCenterX = size * 0.5;
|
|
14966
15026
|
const sceneCenterY = size * 0.56;
|
|
14967
15027
|
const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
|
|
@@ -15058,12 +15118,12 @@
|
|
|
15058
15118
|
x: -faceEyeSpacing,
|
|
15059
15119
|
y: faceEyeYOffset,
|
|
15060
15120
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
|
|
15061
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
15121
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
15062
15122
|
drawProjectedOrganicEye(context, {
|
|
15063
15123
|
x: faceEyeSpacing,
|
|
15064
15124
|
y: faceEyeYOffset,
|
|
15065
15125
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
|
|
15066
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 +
|
|
15126
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
15067
15127
|
drawProjectedOrganicMouth(context, [
|
|
15068
15128
|
{
|
|
15069
15129
|
x: -mouthHalfWidth,
|
|
@@ -15107,14 +15167,28 @@
|
|
|
15107
15167
|
/**
|
|
15108
15168
|
* Draws the soft ground shadow below the octopus.
|
|
15109
15169
|
*
|
|
15170
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
15171
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
15172
|
+
*
|
|
15110
15173
|
* @private helper of `octopus3dAvatarVisual`
|
|
15111
15174
|
*/
|
|
15112
15175
|
function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
|
|
15176
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.04;
|
|
15177
|
+
const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
|
|
15178
|
+
const rx = size * (0.18 + interaction.intensity * 0.02);
|
|
15179
|
+
const ry = size * 0.06;
|
|
15113
15180
|
context.save();
|
|
15114
|
-
context.
|
|
15115
|
-
context.
|
|
15181
|
+
context.translate(cx, cy);
|
|
15182
|
+
context.scale(1, ry / rx);
|
|
15183
|
+
const blurRadius = rx * 1.4;
|
|
15184
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
15185
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
15186
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
15187
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
15188
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
15189
|
+
context.fillStyle = shadowGradient;
|
|
15116
15190
|
context.beginPath();
|
|
15117
|
-
context.
|
|
15191
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
15118
15192
|
context.fill();
|
|
15119
15193
|
context.restore();
|
|
15120
15194
|
}
|
|
@@ -15151,7 +15225,7 @@
|
|
|
15151
15225
|
corners: projectedCorners,
|
|
15152
15226
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
15153
15227
|
transformedCorners.length,
|
|
15154
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
15228
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
15155
15229
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
15156
15230
|
outlineColor,
|
|
15157
15231
|
});
|
|
@@ -15340,11 +15414,40 @@
|
|
|
15340
15414
|
*
|
|
15341
15415
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15342
15416
|
*/
|
|
15343
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
15417
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
15344
15418
|
x: 0.38,
|
|
15345
15419
|
y: -0.6,
|
|
15346
15420
|
z: 0.98,
|
|
15347
15421
|
});
|
|
15422
|
+
/**
|
|
15423
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
15424
|
+
*
|
|
15425
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
15426
|
+
*/
|
|
15427
|
+
const octopus3d2StableStateCache = new WeakMap();
|
|
15428
|
+
/**
|
|
15429
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
15430
|
+
*
|
|
15431
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
15432
|
+
*/
|
|
15433
|
+
function getOctopus3d2StableState(createRandom) {
|
|
15434
|
+
const cached = octopus3d2StableStateCache.get(createRandom);
|
|
15435
|
+
if (cached !== undefined) {
|
|
15436
|
+
return cached;
|
|
15437
|
+
}
|
|
15438
|
+
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
15439
|
+
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
15440
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
15441
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
15442
|
+
const state = {
|
|
15443
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
15444
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
15445
|
+
leftEyePhaseOffset,
|
|
15446
|
+
rightEyePhaseOffset,
|
|
15447
|
+
};
|
|
15448
|
+
octopus3d2StableStateCache.set(createRandom, state);
|
|
15449
|
+
return state;
|
|
15450
|
+
}
|
|
15348
15451
|
/**
|
|
15349
15452
|
* Octopus 3D 2 avatar visual.
|
|
15350
15453
|
*
|
|
@@ -15357,10 +15460,7 @@
|
|
|
15357
15460
|
isAnimated: true,
|
|
15358
15461
|
supportsPointerTracking: true,
|
|
15359
15462
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
15360
|
-
const morphologyProfile =
|
|
15361
|
-
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
15362
|
-
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
15363
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
15463
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
|
|
15364
15464
|
const sceneCenterX = size * 0.5;
|
|
15365
15465
|
const sceneCenterY = size * 0.575;
|
|
15366
15466
|
const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
|
|
@@ -15413,8 +15513,8 @@
|
|
|
15413
15513
|
const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
|
|
15414
15514
|
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
15415
15515
|
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
15416
|
-
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
15417
|
-
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 +
|
|
15516
|
+
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
15517
|
+
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
15418
15518
|
drawProjectedOrganicMouth(context, [
|
|
15419
15519
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
15420
15520
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
@@ -15443,54 +15543,118 @@
|
|
|
15443
15543
|
/**
|
|
15444
15544
|
* Draws the soft floor shadow that anchors the single mesh in the frame.
|
|
15445
15545
|
*
|
|
15546
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
15547
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
15548
|
+
*
|
|
15446
15549
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15447
15550
|
*/
|
|
15448
15551
|
function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
15552
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
15553
|
+
const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
|
|
15554
|
+
const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
|
|
15555
|
+
const ry = size * 0.062;
|
|
15449
15556
|
context.save();
|
|
15450
|
-
context.
|
|
15451
|
-
context.
|
|
15557
|
+
context.translate(cx, cy);
|
|
15558
|
+
context.scale(1, ry / rx);
|
|
15559
|
+
const blurRadius = rx * 1.4;
|
|
15560
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
15561
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
15562
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
15563
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
15564
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
15565
|
+
context.fillStyle = shadowGradient;
|
|
15452
15566
|
context.beginPath();
|
|
15453
|
-
context.
|
|
15567
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
15454
15568
|
context.fill();
|
|
15455
15569
|
context.restore();
|
|
15456
15570
|
}
|
|
15571
|
+
/**
|
|
15572
|
+
* Number of latitude segments used by the single blobby octopus mesh.
|
|
15573
|
+
*
|
|
15574
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
15575
|
+
*/
|
|
15576
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
15577
|
+
/**
|
|
15578
|
+
* Number of longitude segments used by the single blobby octopus mesh.
|
|
15579
|
+
*
|
|
15580
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
15581
|
+
*/
|
|
15582
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
15457
15583
|
/**
|
|
15458
15584
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
15459
15585
|
*
|
|
15586
|
+
* Within a single frame, mesh corner samples and longitude-only lobe-wave values are
|
|
15587
|
+
* quantized to the patch grid and computed once each rather than re-evaluated for every
|
|
15588
|
+
* patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
|
|
15589
|
+
* `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
|
|
15590
|
+
* corners are shared between neighboring patches.
|
|
15591
|
+
*
|
|
15460
15592
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15461
15593
|
*/
|
|
15462
15594
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
15463
15595
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
15464
|
-
const latitudePatchCount =
|
|
15465
|
-
const longitudePatchCount =
|
|
15596
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
15597
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
15466
15598
|
const surfacePatches = [];
|
|
15599
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
15600
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
15601
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
15602
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
15603
|
+
}
|
|
15604
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
15605
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
15606
|
+
}
|
|
15607
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
15608
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
15609
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
|
|
15610
|
+
}
|
|
15611
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
15612
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
15613
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
15614
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
15615
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
15616
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
15617
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
15618
|
+
const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
15619
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
15620
|
+
}
|
|
15621
|
+
}
|
|
15467
15622
|
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
15468
|
-
const startLatitude =
|
|
15469
|
-
const endLatitude =
|
|
15623
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
15624
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
15470
15625
|
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
15471
15626
|
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
15627
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
15628
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
15472
15629
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
15473
|
-
const startLongitude =
|
|
15474
|
-
const endLongitude =
|
|
15630
|
+
const startLongitude = longitudeBoundaries[longitudeIndex];
|
|
15631
|
+
const endLongitude = longitudeBoundaries[longitudeIndex + 1];
|
|
15475
15632
|
const centerLongitude = (startLongitude + endLongitude) / 2;
|
|
15476
|
-
const
|
|
15477
|
-
|
|
15478
|
-
|
|
15479
|
-
|
|
15480
|
-
|
|
15633
|
+
const transformedCorners = [
|
|
15634
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
15635
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
15636
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
15637
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
15481
15638
|
];
|
|
15482
|
-
const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
|
|
15483
15639
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
15484
15640
|
if (surfaceNormal.z <= 0.01) {
|
|
15485
15641
|
continue;
|
|
15486
15642
|
}
|
|
15487
|
-
const projectedCorners =
|
|
15643
|
+
const projectedCorners = [
|
|
15644
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
15645
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
15646
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
15647
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
15648
|
+
];
|
|
15488
15649
|
surfacePatches.push({
|
|
15489
15650
|
corners: projectedCorners,
|
|
15490
|
-
averageDepth: transformedCorners.
|
|
15491
|
-
transformedCorners.
|
|
15492
|
-
|
|
15493
|
-
|
|
15651
|
+
averageDepth: (transformedCorners[0].z +
|
|
15652
|
+
transformedCorners[1].z +
|
|
15653
|
+
transformedCorners[2].z +
|
|
15654
|
+
transformedCorners[3].z) /
|
|
15655
|
+
4,
|
|
15656
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
15657
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
15494
15658
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
15495
15659
|
});
|
|
15496
15660
|
}
|
|
@@ -15506,12 +15670,21 @@
|
|
|
15506
15670
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15507
15671
|
*/
|
|
15508
15672
|
function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
|
|
15673
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
15674
|
+
return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
|
|
15675
|
+
}
|
|
15676
|
+
/**
|
|
15677
|
+
* Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
|
|
15678
|
+
* to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
|
|
15679
|
+
*
|
|
15680
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
15681
|
+
*/
|
|
15682
|
+
function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
|
|
15509
15683
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
15510
15684
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
15511
15685
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
15512
15686
|
const upperBlend = Math.pow(1 - verticalProgress, 1.2);
|
|
15513
15687
|
const lowerBlend = Math.pow(verticalProgress, 1.42);
|
|
15514
|
-
const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
|
|
15515
15688
|
const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
|
|
15516
15689
|
const horizontalScale = 1.02 +
|
|
15517
15690
|
skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
|
|
@@ -15549,7 +15722,7 @@
|
|
|
15549
15722
|
*
|
|
15550
15723
|
* @private helper of `octopus3d2AvatarVisual`
|
|
15551
15724
|
*/
|
|
15552
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
15725
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
15553
15726
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
15554
15727
|
if (tonalProgress < 0.16) {
|
|
15555
15728
|
return palette.highlight;
|
|
@@ -15589,6 +15762,1384 @@
|
|
|
15589
15762
|
context.restore();
|
|
15590
15763
|
}
|
|
15591
15764
|
|
|
15765
|
+
/* eslint-disable no-magic-numbers */
|
|
15766
|
+
/**
|
|
15767
|
+
* Light direction used by the continuous octopus mesh shading.
|
|
15768
|
+
*
|
|
15769
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15770
|
+
*/
|
|
15771
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
15772
|
+
x: 0.34,
|
|
15773
|
+
y: -0.62,
|
|
15774
|
+
z: 1,
|
|
15775
|
+
});
|
|
15776
|
+
/**
|
|
15777
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
15778
|
+
*
|
|
15779
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15780
|
+
*/
|
|
15781
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
15782
|
+
/**
|
|
15783
|
+
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
15784
|
+
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
15785
|
+
* React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
|
|
15786
|
+
*
|
|
15787
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15788
|
+
*/
|
|
15789
|
+
const stableStateCache = new WeakMap();
|
|
15790
|
+
/**
|
|
15791
|
+
* Returns the stable per-avatar state, computing it on first access and returning the cached
|
|
15792
|
+
* result on every subsequent call within the same `<Avatar/>` mount.
|
|
15793
|
+
*
|
|
15794
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15795
|
+
*/
|
|
15796
|
+
function getOctopus3d3StableState(createRandom) {
|
|
15797
|
+
const cached = stableStateCache.get(createRandom);
|
|
15798
|
+
if (cached !== undefined) {
|
|
15799
|
+
return cached;
|
|
15800
|
+
}
|
|
15801
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
15802
|
+
const animationRandom = createRandom('octopus3d3-animation-profile');
|
|
15803
|
+
const eyeRandom = createRandom('octopus3d3-eye-profile');
|
|
15804
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
15805
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
15806
|
+
const state = {
|
|
15807
|
+
morphologyProfile,
|
|
15808
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
15809
|
+
leftEyePhaseOffset,
|
|
15810
|
+
rightEyePhaseOffset,
|
|
15811
|
+
tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
15812
|
+
};
|
|
15813
|
+
stableStateCache.set(createRandom, state);
|
|
15814
|
+
return state;
|
|
15815
|
+
}
|
|
15816
|
+
/**
|
|
15817
|
+
* Octopus 3D 3 avatar visual.
|
|
15818
|
+
*
|
|
15819
|
+
* @private built-in avatar visual
|
|
15820
|
+
*/
|
|
15821
|
+
const octopus3d3AvatarVisual = {
|
|
15822
|
+
id: 'octopus3d3',
|
|
15823
|
+
title: 'Octopus 3D 3',
|
|
15824
|
+
description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
|
|
15825
|
+
isAnimated: true,
|
|
15826
|
+
supportsPointerTracking: true,
|
|
15827
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
15828
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
|
|
15829
|
+
const sceneCenterX = size * 0.5;
|
|
15830
|
+
const sceneCenterY = size * 0.535;
|
|
15831
|
+
const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
|
|
15832
|
+
const meshCenter = {
|
|
15833
|
+
x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
15834
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
15835
|
+
z: interaction.intensity * size * 0.018,
|
|
15836
|
+
};
|
|
15837
|
+
const rotationY = -0.1 +
|
|
15838
|
+
Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
|
|
15839
|
+
interaction.bodyOffsetX * 0.22 +
|
|
15840
|
+
interaction.gazeX * 0.88;
|
|
15841
|
+
const rotationX = -0.07 +
|
|
15842
|
+
Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
|
|
15843
|
+
interaction.bodyOffsetY * 0.08 -
|
|
15844
|
+
interaction.gazeY * 0.38;
|
|
15845
|
+
const surfaceOptions = {
|
|
15846
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
|
|
15847
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
|
|
15848
|
+
radiusZ: size *
|
|
15849
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
15850
|
+
(1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
|
|
15851
|
+
morphologyProfile,
|
|
15852
|
+
timeMs,
|
|
15853
|
+
animationPhase,
|
|
15854
|
+
tentacleProfiles,
|
|
15855
|
+
};
|
|
15856
|
+
const surfacePatches = resolveVisibleContinuousOctopusPatches({
|
|
15857
|
+
...surfaceOptions,
|
|
15858
|
+
center: meshCenter,
|
|
15859
|
+
rotationX,
|
|
15860
|
+
rotationY,
|
|
15861
|
+
sceneCenterX,
|
|
15862
|
+
sceneCenterY,
|
|
15863
|
+
size,
|
|
15864
|
+
palette,
|
|
15865
|
+
});
|
|
15866
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
|
|
15867
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
|
|
15868
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
15869
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
15870
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
15871
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
15872
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
|
|
15873
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
|
|
15874
|
+
drawAvatarFrame(context, size, palette);
|
|
15875
|
+
drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
15876
|
+
drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
15877
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
15878
|
+
drawContinuousSurfacePatch(context, surfacePatch);
|
|
15879
|
+
}
|
|
15880
|
+
drawProjectedSurfaceCurrents({
|
|
15881
|
+
context,
|
|
15882
|
+
surfaceOptions,
|
|
15883
|
+
center: meshCenter,
|
|
15884
|
+
rotationX,
|
|
15885
|
+
rotationY,
|
|
15886
|
+
sceneCenterX,
|
|
15887
|
+
sceneCenterY,
|
|
15888
|
+
size,
|
|
15889
|
+
palette,
|
|
15890
|
+
morphologyProfile,
|
|
15891
|
+
timeMs,
|
|
15892
|
+
animationPhase,
|
|
15893
|
+
});
|
|
15894
|
+
drawProjectedTentacleSuckers({
|
|
15895
|
+
context,
|
|
15896
|
+
surfaceOptions,
|
|
15897
|
+
center: meshCenter,
|
|
15898
|
+
rotationX,
|
|
15899
|
+
rotationY,
|
|
15900
|
+
sceneCenterX,
|
|
15901
|
+
sceneCenterY,
|
|
15902
|
+
size,
|
|
15903
|
+
palette,
|
|
15904
|
+
});
|
|
15905
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
15906
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
15907
|
+
drawProjectedOrganicMouth(context, [
|
|
15908
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
15909
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
15910
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
15911
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
15912
|
+
},
|
|
15913
|
+
};
|
|
15914
|
+
/**
|
|
15915
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
15916
|
+
*
|
|
15917
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15918
|
+
*/
|
|
15919
|
+
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
15920
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
15921
|
+
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
15922
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
15923
|
+
return {
|
|
15924
|
+
centerLongitude: -Math.PI * 0.86 +
|
|
15925
|
+
progress * Math.PI * 1.72 +
|
|
15926
|
+
(tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
|
|
15927
|
+
widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
|
|
15928
|
+
lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
|
|
15929
|
+
swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
|
|
15930
|
+
depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
|
|
15931
|
+
phase: tentacleRandom() * Math.PI * 2,
|
|
15932
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
15933
|
+
};
|
|
15934
|
+
});
|
|
15935
|
+
}
|
|
15936
|
+
/**
|
|
15937
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
15938
|
+
*
|
|
15939
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15940
|
+
*/
|
|
15941
|
+
function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
15942
|
+
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));
|
|
15943
|
+
glowGradient.addColorStop(0, `${palette.highlight}66`);
|
|
15944
|
+
glowGradient.addColorStop(0.34, `${palette.accent}2e`);
|
|
15945
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
15946
|
+
context.fillStyle = glowGradient;
|
|
15947
|
+
context.fillRect(0, 0, size, size);
|
|
15948
|
+
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);
|
|
15949
|
+
lowerGradient.addColorStop(0, `${palette.secondary}25`);
|
|
15950
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
15951
|
+
context.fillStyle = lowerGradient;
|
|
15952
|
+
context.fillRect(0, 0, size, size);
|
|
15953
|
+
}
|
|
15954
|
+
/**
|
|
15955
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
15956
|
+
*
|
|
15957
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
15958
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
15959
|
+
*
|
|
15960
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15961
|
+
*/
|
|
15962
|
+
function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
15963
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
15964
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
15965
|
+
const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
|
|
15966
|
+
const ry = size * 0.06;
|
|
15967
|
+
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
15968
|
+
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
15969
|
+
context.save();
|
|
15970
|
+
context.translate(cx, cy);
|
|
15971
|
+
context.scale(1, ry / rx);
|
|
15972
|
+
const blurRadius = rx * 1.4;
|
|
15973
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
15974
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
15975
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
15976
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
15977
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
15978
|
+
context.fillStyle = shadowGradient;
|
|
15979
|
+
context.beginPath();
|
|
15980
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
15981
|
+
context.fill();
|
|
15982
|
+
context.restore();
|
|
15983
|
+
}
|
|
15984
|
+
/**
|
|
15985
|
+
* Number of latitude segments used by the continuous Octopus 3D 3 mesh.
|
|
15986
|
+
*
|
|
15987
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15988
|
+
*/
|
|
15989
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
15990
|
+
/**
|
|
15991
|
+
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
15992
|
+
*
|
|
15993
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
15994
|
+
*/
|
|
15995
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
15996
|
+
/**
|
|
15997
|
+
* Resolves visible projected patches for the continuous octopus mesh.
|
|
15998
|
+
*
|
|
15999
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
16000
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
16001
|
+
* than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
|
|
16002
|
+
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
16003
|
+
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
16004
|
+
*
|
|
16005
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16006
|
+
*/
|
|
16007
|
+
function resolveVisibleContinuousOctopusPatches(options) {
|
|
16008
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
16009
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
16010
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
16011
|
+
const surfacePatches = [];
|
|
16012
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16013
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
16014
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
16015
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
16016
|
+
}
|
|
16017
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16018
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
16019
|
+
}
|
|
16020
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
16021
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
16022
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
16023
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
16024
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
16025
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16026
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
16027
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
|
|
16028
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
|
|
16029
|
+
}
|
|
16030
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16031
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
16032
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
|
|
16033
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
|
|
16034
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
16035
|
+
}
|
|
16036
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
16037
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
16038
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
16039
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
16040
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
16041
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
16042
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
16043
|
+
const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
16044
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
16045
|
+
}
|
|
16046
|
+
}
|
|
16047
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
16048
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
16049
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
16050
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
16051
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
16052
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
16053
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
16054
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16055
|
+
const transformedCorners = [
|
|
16056
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
16057
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
16058
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
16059
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
16060
|
+
];
|
|
16061
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
16062
|
+
if (surfaceNormal.z <= 0.008) {
|
|
16063
|
+
continue;
|
|
16064
|
+
}
|
|
16065
|
+
const projectedCorners = [
|
|
16066
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
16067
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
16068
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
16069
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
16070
|
+
];
|
|
16071
|
+
surfacePatches.push({
|
|
16072
|
+
corners: projectedCorners,
|
|
16073
|
+
averageDepth: (transformedCorners[0].z +
|
|
16074
|
+
transformedCorners[1].z +
|
|
16075
|
+
transformedCorners[2].z +
|
|
16076
|
+
transformedCorners[3].z) /
|
|
16077
|
+
4,
|
|
16078
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
16079
|
+
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
16080
|
+
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
16081
|
+
});
|
|
16082
|
+
}
|
|
16083
|
+
}
|
|
16084
|
+
return surfacePatches;
|
|
16085
|
+
}
|
|
16086
|
+
/**
|
|
16087
|
+
* Samples one point on the continuous Octopus 3D 3 surface.
|
|
16088
|
+
*
|
|
16089
|
+
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
16090
|
+
* tentacled while still being rendered as one connected blobby mesh.
|
|
16091
|
+
*
|
|
16092
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16093
|
+
*/
|
|
16094
|
+
function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
|
|
16095
|
+
return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
|
|
16096
|
+
}
|
|
16097
|
+
/**
|
|
16098
|
+
* Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
|
|
16099
|
+
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
16100
|
+
*
|
|
16101
|
+
* The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
|
|
16102
|
+
* corner grid, so the same longitude is reused across every latitude row and each
|
|
16103
|
+
* tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
|
|
16104
|
+
*
|
|
16105
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16106
|
+
*/
|
|
16107
|
+
function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
16108
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
16109
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
16110
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
16111
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
16112
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
16113
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
16114
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
16115
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
16116
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
16117
|
+
animationPhase * 0.6 +
|
|
16118
|
+
timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
|
|
16119
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
|
|
16120
|
+
(0.3 + lowerBlend * 0.7);
|
|
16121
|
+
const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
|
|
16122
|
+
lowerBlend *
|
|
16123
|
+
tentacleInfluence.core *
|
|
16124
|
+
tentacleInfluence.swayScale;
|
|
16125
|
+
const horizontalScale = 1.04 +
|
|
16126
|
+
mantleRipple +
|
|
16127
|
+
lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
|
|
16128
|
+
lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
|
|
16129
|
+
upperBlend * 0.08;
|
|
16130
|
+
const depthScale = 1.06 +
|
|
16131
|
+
upperBlend * 0.16 +
|
|
16132
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
|
|
16133
|
+
lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
|
|
16134
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
16135
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
|
|
16136
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
16137
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
|
|
16138
|
+
const lowerDrop = lowerBlend *
|
|
16139
|
+
radiusY *
|
|
16140
|
+
(0.18 +
|
|
16141
|
+
tentacleInfluence.core *
|
|
16142
|
+
(0.38 +
|
|
16143
|
+
tentacleInfluence.lengthScale * 0.22 +
|
|
16144
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
|
|
16145
|
+
return {
|
|
16146
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
|
|
16147
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
|
|
16148
|
+
upperBlend * radiusY * 0.1 +
|
|
16149
|
+
lowerDrop +
|
|
16150
|
+
Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
|
|
16151
|
+
Math.cos(timeMs / 880 + tentacleInfluence.phase) *
|
|
16152
|
+
lowerBlend *
|
|
16153
|
+
tipBlend *
|
|
16154
|
+
tentacleInfluence.core *
|
|
16155
|
+
radiusY *
|
|
16156
|
+
0.034,
|
|
16157
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
16158
|
+
Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
|
|
16159
|
+
lowerBlend *
|
|
16160
|
+
tentacleInfluence.core *
|
|
16161
|
+
radiusZ *
|
|
16162
|
+
0.04,
|
|
16163
|
+
};
|
|
16164
|
+
}
|
|
16165
|
+
/**
|
|
16166
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
16167
|
+
*
|
|
16168
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16169
|
+
*/
|
|
16170
|
+
function resolveContinuousTentacleInfluence(options, longitude) {
|
|
16171
|
+
let totalWeight = 0;
|
|
16172
|
+
let weightedSin = 0;
|
|
16173
|
+
let weightedCos = 0;
|
|
16174
|
+
let weightedWidthScale = 0;
|
|
16175
|
+
let weightedLengthScale = 0;
|
|
16176
|
+
let weightedSwayScale = 0;
|
|
16177
|
+
let weightedDepthScale = 0;
|
|
16178
|
+
let weightedPhase = 0;
|
|
16179
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
16180
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
16181
|
+
const width = 0.2 * tentacleProfile.widthScale;
|
|
16182
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
16183
|
+
totalWeight += weight;
|
|
16184
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
16185
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
16186
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
16187
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
16188
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
16189
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
16190
|
+
weightedPhase += tentacleProfile.phase * weight;
|
|
16191
|
+
}
|
|
16192
|
+
if (totalWeight < 0.0001) {
|
|
16193
|
+
return {
|
|
16194
|
+
core: 0,
|
|
16195
|
+
centerLongitude: longitude,
|
|
16196
|
+
widthScale: 1,
|
|
16197
|
+
lengthScale: 1,
|
|
16198
|
+
swayScale: 1,
|
|
16199
|
+
depthScale: 1,
|
|
16200
|
+
phase: 0,
|
|
16201
|
+
};
|
|
16202
|
+
}
|
|
16203
|
+
return {
|
|
16204
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
16205
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
16206
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
16207
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
16208
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
16209
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
16210
|
+
phase: weightedPhase / totalWeight,
|
|
16211
|
+
};
|
|
16212
|
+
}
|
|
16213
|
+
/**
|
|
16214
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
16215
|
+
*
|
|
16216
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16217
|
+
*/
|
|
16218
|
+
function resolveContinuousLobeWave(options, longitude) {
|
|
16219
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
16220
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
16221
|
+
animationPhase +
|
|
16222
|
+
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
16223
|
+
1) /
|
|
16224
|
+
2);
|
|
16225
|
+
}
|
|
16226
|
+
/**
|
|
16227
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
16228
|
+
*
|
|
16229
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16230
|
+
*/
|
|
16231
|
+
function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
16232
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
16233
|
+
if (tonalProgress < 0.14) {
|
|
16234
|
+
return palette.highlight;
|
|
16235
|
+
}
|
|
16236
|
+
if (tonalProgress < 0.32) {
|
|
16237
|
+
return palette.secondary;
|
|
16238
|
+
}
|
|
16239
|
+
if (tonalProgress < 0.72) {
|
|
16240
|
+
return forwardness > 0.55 ? palette.secondary : palette.primary;
|
|
16241
|
+
}
|
|
16242
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
16243
|
+
}
|
|
16244
|
+
/**
|
|
16245
|
+
* Draws one projected mesh patch with soft shading and a subtle edge.
|
|
16246
|
+
*
|
|
16247
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16248
|
+
*/
|
|
16249
|
+
function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
16250
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
16251
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
16252
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
|
|
16253
|
+
}
|
|
16254
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
16255
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
16256
|
+
}
|
|
16257
|
+
context.save();
|
|
16258
|
+
context.beginPath();
|
|
16259
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
16260
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
16261
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
16262
|
+
}
|
|
16263
|
+
context.closePath();
|
|
16264
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
16265
|
+
context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
|
|
16266
|
+
context.lineJoin = 'round';
|
|
16267
|
+
context.stroke();
|
|
16268
|
+
context.restore();
|
|
16269
|
+
}
|
|
16270
|
+
/**
|
|
16271
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
16272
|
+
*
|
|
16273
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16274
|
+
*/
|
|
16275
|
+
function drawProjectedSurfaceCurrents(options) {
|
|
16276
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
16277
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
16278
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
16279
|
+
context.save();
|
|
16280
|
+
context.lineCap = 'round';
|
|
16281
|
+
context.lineJoin = 'round';
|
|
16282
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
16283
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
16284
|
+
const projectedPoints = [];
|
|
16285
|
+
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
|
|
16286
|
+
const progress = sampleIndex / 7;
|
|
16287
|
+
const latitude = -0.46 + progress * 0.74;
|
|
16288
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
|
|
16289
|
+
const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
16290
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
16291
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
16292
|
+
}
|
|
16293
|
+
}
|
|
16294
|
+
if (projectedPoints.length < 3) {
|
|
16295
|
+
continue;
|
|
16296
|
+
}
|
|
16297
|
+
context.beginPath();
|
|
16298
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
16299
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
16300
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
16301
|
+
}
|
|
16302
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
16303
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
16304
|
+
context.stroke();
|
|
16305
|
+
}
|
|
16306
|
+
context.restore();
|
|
16307
|
+
}
|
|
16308
|
+
/**
|
|
16309
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
16310
|
+
*
|
|
16311
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16312
|
+
*/
|
|
16313
|
+
function drawProjectedTentacleSuckers(options) {
|
|
16314
|
+
const { surfaceOptions, size } = options;
|
|
16315
|
+
const { timeMs } = surfaceOptions;
|
|
16316
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
16317
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
|
|
16318
|
+
continue;
|
|
16319
|
+
}
|
|
16320
|
+
for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
|
|
16321
|
+
const latitude = 0.52 + suckerIndex * 0.14;
|
|
16322
|
+
const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
|
|
16323
|
+
const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
|
|
16324
|
+
drawProjectedSurfaceSpot({
|
|
16325
|
+
...options,
|
|
16326
|
+
latitude,
|
|
16327
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
16328
|
+
radiusScale: size * (0.0065 - suckerIndex * 0.0007),
|
|
16329
|
+
});
|
|
16330
|
+
}
|
|
16331
|
+
}
|
|
16332
|
+
}
|
|
16333
|
+
/**
|
|
16334
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
16335
|
+
*
|
|
16336
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16337
|
+
*/
|
|
16338
|
+
function drawProjectedSurfaceSpot(options) {
|
|
16339
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
16340
|
+
const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
|
|
16341
|
+
const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
16342
|
+
const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
16343
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
16344
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
16345
|
+
return;
|
|
16346
|
+
}
|
|
16347
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
16348
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
16349
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
16350
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
16351
|
+
radiusScale *
|
|
16352
|
+
0.74, size * 0.003, size * 0.018);
|
|
16353
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
16354
|
+
radiusScale *
|
|
16355
|
+
0.52, size * 0.0024, size * 0.014);
|
|
16356
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
16357
|
+
context.save();
|
|
16358
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
16359
|
+
context.rotate(rotation);
|
|
16360
|
+
context.beginPath();
|
|
16361
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
16362
|
+
context.fillStyle = `${palette.highlight}73`;
|
|
16363
|
+
context.fill();
|
|
16364
|
+
context.strokeStyle = `${palette.highlight}99`;
|
|
16365
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
16366
|
+
context.stroke();
|
|
16367
|
+
context.restore();
|
|
16368
|
+
}
|
|
16369
|
+
/**
|
|
16370
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
16371
|
+
*
|
|
16372
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16373
|
+
*/
|
|
16374
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
16375
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
16376
|
+
}
|
|
16377
|
+
/**
|
|
16378
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
16379
|
+
*
|
|
16380
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16381
|
+
*/
|
|
16382
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
16383
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
16384
|
+
return progress * progress * (3 - 2 * progress);
|
|
16385
|
+
}
|
|
16386
|
+
|
|
16387
|
+
/* eslint-disable no-magic-numbers */
|
|
16388
|
+
/**
|
|
16389
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
16390
|
+
*
|
|
16391
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16392
|
+
*/
|
|
16393
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
16394
|
+
x: 0.32,
|
|
16395
|
+
y: -0.66,
|
|
16396
|
+
z: 1,
|
|
16397
|
+
});
|
|
16398
|
+
/**
|
|
16399
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
16400
|
+
*
|
|
16401
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16402
|
+
*/
|
|
16403
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
16404
|
+
x: -0.7,
|
|
16405
|
+
y: -0.18,
|
|
16406
|
+
z: 0.55,
|
|
16407
|
+
});
|
|
16408
|
+
/**
|
|
16409
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
16410
|
+
*
|
|
16411
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16412
|
+
*/
|
|
16413
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
16414
|
+
/**
|
|
16415
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
16416
|
+
*
|
|
16417
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16418
|
+
*/
|
|
16419
|
+
const SKIN_SPOT_COUNT = 14;
|
|
16420
|
+
/**
|
|
16421
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
16422
|
+
*
|
|
16423
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16424
|
+
*/
|
|
16425
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
16426
|
+
/**
|
|
16427
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
16428
|
+
*
|
|
16429
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16430
|
+
*/
|
|
16431
|
+
function getOctopus3d4StableState(createRandom) {
|
|
16432
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
16433
|
+
if (cached !== undefined) {
|
|
16434
|
+
return cached;
|
|
16435
|
+
}
|
|
16436
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
16437
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
16438
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
16439
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
16440
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
16441
|
+
const state = {
|
|
16442
|
+
morphologyProfile,
|
|
16443
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16444
|
+
leftEyePhaseOffset,
|
|
16445
|
+
rightEyePhaseOffset,
|
|
16446
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
16447
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
16448
|
+
};
|
|
16449
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
16450
|
+
return state;
|
|
16451
|
+
}
|
|
16452
|
+
/**
|
|
16453
|
+
* Octopus 3D 4 avatar visual.
|
|
16454
|
+
*
|
|
16455
|
+
* @private built-in avatar visual
|
|
16456
|
+
*/
|
|
16457
|
+
const octopus3d4AvatarVisual = {
|
|
16458
|
+
id: 'octopus3d4',
|
|
16459
|
+
title: 'Octopus 3D 4',
|
|
16460
|
+
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.',
|
|
16461
|
+
isAnimated: true,
|
|
16462
|
+
supportsPointerTracking: true,
|
|
16463
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
16464
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
16465
|
+
const sceneCenterX = size * 0.5;
|
|
16466
|
+
const sceneCenterY = size * 0.535;
|
|
16467
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
16468
|
+
const meshCenter = {
|
|
16469
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
16470
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
16471
|
+
z: interaction.intensity * size * 0.02,
|
|
16472
|
+
};
|
|
16473
|
+
const rotationY = -0.08 +
|
|
16474
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
16475
|
+
interaction.bodyOffsetX * 0.24 +
|
|
16476
|
+
interaction.gazeX * 0.98;
|
|
16477
|
+
const rotationX = -0.07 +
|
|
16478
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
16479
|
+
interaction.bodyOffsetY * 0.08 -
|
|
16480
|
+
interaction.gazeY * 0.42;
|
|
16481
|
+
const surfaceOptions = {
|
|
16482
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
16483
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
16484
|
+
radiusZ: size *
|
|
16485
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
16486
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
16487
|
+
morphologyProfile,
|
|
16488
|
+
timeMs,
|
|
16489
|
+
animationPhase,
|
|
16490
|
+
tentacleProfiles,
|
|
16491
|
+
};
|
|
16492
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
16493
|
+
...surfaceOptions,
|
|
16494
|
+
center: meshCenter,
|
|
16495
|
+
rotationX,
|
|
16496
|
+
rotationY,
|
|
16497
|
+
sceneCenterX,
|
|
16498
|
+
sceneCenterY,
|
|
16499
|
+
size,
|
|
16500
|
+
palette,
|
|
16501
|
+
});
|
|
16502
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
16503
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
16504
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
16505
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
16506
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
16507
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
16508
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
16509
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
16510
|
+
drawAvatarFrame(context, size, palette);
|
|
16511
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
16512
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
16513
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
16514
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
16515
|
+
}
|
|
16516
|
+
drawBlobbySkinSpots({
|
|
16517
|
+
context,
|
|
16518
|
+
surfaceOptions,
|
|
16519
|
+
center: meshCenter,
|
|
16520
|
+
rotationX,
|
|
16521
|
+
rotationY,
|
|
16522
|
+
sceneCenterX,
|
|
16523
|
+
sceneCenterY,
|
|
16524
|
+
size,
|
|
16525
|
+
palette,
|
|
16526
|
+
skinSpots,
|
|
16527
|
+
});
|
|
16528
|
+
drawBlobbyContinuousCurrents({
|
|
16529
|
+
context,
|
|
16530
|
+
surfaceOptions,
|
|
16531
|
+
center: meshCenter,
|
|
16532
|
+
rotationX,
|
|
16533
|
+
rotationY,
|
|
16534
|
+
sceneCenterX,
|
|
16535
|
+
sceneCenterY,
|
|
16536
|
+
size,
|
|
16537
|
+
palette,
|
|
16538
|
+
morphologyProfile,
|
|
16539
|
+
timeMs,
|
|
16540
|
+
animationPhase,
|
|
16541
|
+
});
|
|
16542
|
+
drawBlobbyContinuousSuckers({
|
|
16543
|
+
context,
|
|
16544
|
+
surfaceOptions,
|
|
16545
|
+
center: meshCenter,
|
|
16546
|
+
rotationX,
|
|
16547
|
+
rotationY,
|
|
16548
|
+
sceneCenterX,
|
|
16549
|
+
sceneCenterY,
|
|
16550
|
+
size,
|
|
16551
|
+
palette,
|
|
16552
|
+
});
|
|
16553
|
+
drawBlobbyContinuousGloss({
|
|
16554
|
+
context,
|
|
16555
|
+
surfaceOptions,
|
|
16556
|
+
center: meshCenter,
|
|
16557
|
+
rotationX,
|
|
16558
|
+
rotationY,
|
|
16559
|
+
sceneCenterX,
|
|
16560
|
+
sceneCenterY,
|
|
16561
|
+
size,
|
|
16562
|
+
palette,
|
|
16563
|
+
});
|
|
16564
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16565
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16566
|
+
drawProjectedOrganicMouth(context, [
|
|
16567
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
16568
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
16569
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
16570
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
16571
|
+
},
|
|
16572
|
+
};
|
|
16573
|
+
/**
|
|
16574
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
16575
|
+
*
|
|
16576
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16577
|
+
*/
|
|
16578
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
16579
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
16580
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
16581
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
16582
|
+
return {
|
|
16583
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
16584
|
+
progress * Math.PI * 1.8 +
|
|
16585
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
16586
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
16587
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
16588
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
16589
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
16590
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
16591
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
16592
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
16593
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
16594
|
+
};
|
|
16595
|
+
});
|
|
16596
|
+
}
|
|
16597
|
+
/**
|
|
16598
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
16599
|
+
*
|
|
16600
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16601
|
+
*/
|
|
16602
|
+
function createBlobbySkinSpots(createRandom) {
|
|
16603
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
16604
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
16605
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
16606
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
16607
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
16608
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
16609
|
+
}));
|
|
16610
|
+
}
|
|
16611
|
+
/**
|
|
16612
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
16613
|
+
*
|
|
16614
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16615
|
+
*/
|
|
16616
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
16617
|
+
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));
|
|
16618
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
16619
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
16620
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
16621
|
+
context.fillStyle = glowGradient;
|
|
16622
|
+
context.fillRect(0, 0, size, size);
|
|
16623
|
+
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);
|
|
16624
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
16625
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
16626
|
+
context.fillStyle = lowerGradient;
|
|
16627
|
+
context.fillRect(0, 0, size, size);
|
|
16628
|
+
}
|
|
16629
|
+
/**
|
|
16630
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
16631
|
+
*
|
|
16632
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16633
|
+
*/
|
|
16634
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
16635
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
16636
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
16637
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
16638
|
+
const ry = size * 0.062;
|
|
16639
|
+
context.save();
|
|
16640
|
+
context.translate(cx, cy);
|
|
16641
|
+
context.scale(1, ry / rx);
|
|
16642
|
+
const blurRadius = rx * 1.42;
|
|
16643
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
16644
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
16645
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
16646
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
16647
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
16648
|
+
context.fillStyle = shadowGradient;
|
|
16649
|
+
context.beginPath();
|
|
16650
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
16651
|
+
context.fill();
|
|
16652
|
+
context.restore();
|
|
16653
|
+
}
|
|
16654
|
+
/**
|
|
16655
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
16656
|
+
*
|
|
16657
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16658
|
+
*/
|
|
16659
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
16660
|
+
/**
|
|
16661
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
16662
|
+
*
|
|
16663
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16664
|
+
*/
|
|
16665
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
16666
|
+
/**
|
|
16667
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
16668
|
+
*
|
|
16669
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
16670
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
16671
|
+
* than re-evaluated for every patch corner.
|
|
16672
|
+
*
|
|
16673
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16674
|
+
*/
|
|
16675
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
16676
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
16677
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
16678
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
16679
|
+
const surfacePatches = [];
|
|
16680
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16681
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
16682
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
16683
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
16684
|
+
}
|
|
16685
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16686
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
16687
|
+
}
|
|
16688
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
16689
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
16690
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
16691
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
16692
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
16693
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16694
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
16695
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
16696
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
16697
|
+
}
|
|
16698
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16699
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
16700
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
16701
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
16702
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
16703
|
+
}
|
|
16704
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
16705
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
16706
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
16707
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
16708
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
16709
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
16710
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
16711
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
16712
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
16713
|
+
}
|
|
16714
|
+
}
|
|
16715
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
16716
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
16717
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
16718
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
16719
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
16720
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
16721
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
16722
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16723
|
+
const transformedCorners = [
|
|
16724
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
16725
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
16726
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
16727
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
16728
|
+
];
|
|
16729
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
16730
|
+
if (surfaceNormal.z <= 0.006) {
|
|
16731
|
+
continue;
|
|
16732
|
+
}
|
|
16733
|
+
const projectedCorners = [
|
|
16734
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
16735
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
16736
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
16737
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
16738
|
+
];
|
|
16739
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
16740
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
16741
|
+
surfacePatches.push({
|
|
16742
|
+
corners: projectedCorners,
|
|
16743
|
+
averageDepth: (transformedCorners[0].z +
|
|
16744
|
+
transformedCorners[1].z +
|
|
16745
|
+
transformedCorners[2].z +
|
|
16746
|
+
transformedCorners[3].z) /
|
|
16747
|
+
4,
|
|
16748
|
+
lightIntensity,
|
|
16749
|
+
rimLightIntensity,
|
|
16750
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
16751
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
16752
|
+
});
|
|
16753
|
+
}
|
|
16754
|
+
}
|
|
16755
|
+
return surfacePatches;
|
|
16756
|
+
}
|
|
16757
|
+
/**
|
|
16758
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
16759
|
+
*
|
|
16760
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16761
|
+
*/
|
|
16762
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
16763
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
16764
|
+
}
|
|
16765
|
+
/**
|
|
16766
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
16767
|
+
*
|
|
16768
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
16769
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
16770
|
+
* of `latitudePatchCount * 4` times.
|
|
16771
|
+
*
|
|
16772
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16773
|
+
*/
|
|
16774
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
16775
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
16776
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
16777
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
16778
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
16779
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
16780
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
16781
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
16782
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
16783
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
16784
|
+
animationPhase * 0.6 +
|
|
16785
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
16786
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
16787
|
+
(0.32 + lowerBlend * 0.7);
|
|
16788
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
16789
|
+
lowerBlend *
|
|
16790
|
+
tentacleInfluence.core *
|
|
16791
|
+
tentacleInfluence.swayScale;
|
|
16792
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
16793
|
+
lowerBlend *
|
|
16794
|
+
tentacleInfluence.core *
|
|
16795
|
+
tentacleInfluence.swayScale *
|
|
16796
|
+
0.42;
|
|
16797
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
16798
|
+
tipBlend *
|
|
16799
|
+
tentacleInfluence.core *
|
|
16800
|
+
tentacleInfluence.curlScale *
|
|
16801
|
+
0.36;
|
|
16802
|
+
const horizontalScale = 1.06 +
|
|
16803
|
+
mantleRipple +
|
|
16804
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
16805
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
16806
|
+
upperBlend * 0.08;
|
|
16807
|
+
const depthScale = 1.08 +
|
|
16808
|
+
upperBlend * 0.16 +
|
|
16809
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
16810
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
16811
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
16812
|
+
const tentacleTubeRadius = lowerBlend *
|
|
16813
|
+
tentacleInfluence.core *
|
|
16814
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
16815
|
+
radiusX;
|
|
16816
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
16817
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
16818
|
+
const lowerDrop = lowerBlend *
|
|
16819
|
+
radiusY *
|
|
16820
|
+
(0.2 +
|
|
16821
|
+
tentacleInfluence.core *
|
|
16822
|
+
(0.42 +
|
|
16823
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
16824
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
16825
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
16826
|
+
return {
|
|
16827
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
16828
|
+
combinedTentacleSway +
|
|
16829
|
+
tentacleCurl * radiusX * 0.18,
|
|
16830
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
16831
|
+
upperBlend * radiusY * 0.12 +
|
|
16832
|
+
lowerDrop +
|
|
16833
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
16834
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
16835
|
+
lowerBlend *
|
|
16836
|
+
tipBlend *
|
|
16837
|
+
tentacleInfluence.core *
|
|
16838
|
+
radiusY *
|
|
16839
|
+
0.04,
|
|
16840
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
16841
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
16842
|
+
lowerBlend *
|
|
16843
|
+
tentacleInfluence.core *
|
|
16844
|
+
radiusZ *
|
|
16845
|
+
0.044 +
|
|
16846
|
+
tentacleCurl * radiusZ * 0.14,
|
|
16847
|
+
};
|
|
16848
|
+
}
|
|
16849
|
+
/**
|
|
16850
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
16851
|
+
*
|
|
16852
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16853
|
+
*/
|
|
16854
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
16855
|
+
let totalWeight = 0;
|
|
16856
|
+
let weightedSin = 0;
|
|
16857
|
+
let weightedCos = 0;
|
|
16858
|
+
let weightedWidthScale = 0;
|
|
16859
|
+
let weightedLengthScale = 0;
|
|
16860
|
+
let weightedSwayScale = 0;
|
|
16861
|
+
let weightedDepthScale = 0;
|
|
16862
|
+
let weightedCurlScale = 0;
|
|
16863
|
+
let weightedPrimaryPhase = 0;
|
|
16864
|
+
let weightedSecondaryPhase = 0;
|
|
16865
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
16866
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
16867
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
16868
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
16869
|
+
totalWeight += weight;
|
|
16870
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
16871
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
16872
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
16873
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
16874
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
16875
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
16876
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
16877
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
16878
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
16879
|
+
}
|
|
16880
|
+
if (totalWeight < 0.0001) {
|
|
16881
|
+
return {
|
|
16882
|
+
core: 0,
|
|
16883
|
+
centerLongitude: longitude,
|
|
16884
|
+
widthScale: 1,
|
|
16885
|
+
lengthScale: 1,
|
|
16886
|
+
swayScale: 1,
|
|
16887
|
+
depthScale: 1,
|
|
16888
|
+
curlScale: 1,
|
|
16889
|
+
primaryPhase: 0,
|
|
16890
|
+
secondaryPhase: 0,
|
|
16891
|
+
};
|
|
16892
|
+
}
|
|
16893
|
+
return {
|
|
16894
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
16895
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
16896
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
16897
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
16898
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
16899
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
16900
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
16901
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
16902
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
16903
|
+
};
|
|
16904
|
+
}
|
|
16905
|
+
/**
|
|
16906
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
16907
|
+
*
|
|
16908
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16909
|
+
*/
|
|
16910
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
16911
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
16912
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
16913
|
+
animationPhase +
|
|
16914
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
16915
|
+
1) /
|
|
16916
|
+
2);
|
|
16917
|
+
}
|
|
16918
|
+
/**
|
|
16919
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
16920
|
+
*
|
|
16921
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16922
|
+
*/
|
|
16923
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
16924
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
16925
|
+
if (tonalProgress < 0.12) {
|
|
16926
|
+
return palette.highlight;
|
|
16927
|
+
}
|
|
16928
|
+
if (tonalProgress < 0.3) {
|
|
16929
|
+
return palette.secondary;
|
|
16930
|
+
}
|
|
16931
|
+
if (tonalProgress < 0.72) {
|
|
16932
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
16933
|
+
}
|
|
16934
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
16935
|
+
}
|
|
16936
|
+
/**
|
|
16937
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
16938
|
+
*
|
|
16939
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16940
|
+
*/
|
|
16941
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
16942
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
16943
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
16944
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
16945
|
+
}
|
|
16946
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
16947
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
16948
|
+
}
|
|
16949
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
16950
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
16951
|
+
}
|
|
16952
|
+
context.save();
|
|
16953
|
+
context.beginPath();
|
|
16954
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
16955
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
16956
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
16957
|
+
}
|
|
16958
|
+
context.closePath();
|
|
16959
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
16960
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
16961
|
+
context.lineJoin = 'round';
|
|
16962
|
+
context.stroke();
|
|
16963
|
+
context.restore();
|
|
16964
|
+
}
|
|
16965
|
+
/**
|
|
16966
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
16967
|
+
*
|
|
16968
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
16969
|
+
*/
|
|
16970
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
16971
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
16972
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
16973
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
16974
|
+
context.save();
|
|
16975
|
+
context.lineCap = 'round';
|
|
16976
|
+
context.lineJoin = 'round';
|
|
16977
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
16978
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
16979
|
+
const projectedPoints = [];
|
|
16980
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
16981
|
+
const progress = sampleIndex / 8;
|
|
16982
|
+
const latitude = -0.48 + progress * 0.78;
|
|
16983
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
16984
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
16985
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
16986
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
16987
|
+
}
|
|
16988
|
+
}
|
|
16989
|
+
if (projectedPoints.length < 3) {
|
|
16990
|
+
continue;
|
|
16991
|
+
}
|
|
16992
|
+
context.beginPath();
|
|
16993
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
16994
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
16995
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
16996
|
+
}
|
|
16997
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
16998
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
16999
|
+
context.stroke();
|
|
17000
|
+
}
|
|
17001
|
+
context.restore();
|
|
17002
|
+
}
|
|
17003
|
+
/**
|
|
17004
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
17005
|
+
*
|
|
17006
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17007
|
+
*/
|
|
17008
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
17009
|
+
const { surfaceOptions, size } = options;
|
|
17010
|
+
const { timeMs } = surfaceOptions;
|
|
17011
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
17012
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
17013
|
+
continue;
|
|
17014
|
+
}
|
|
17015
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
17016
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
17017
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
17018
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
17019
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
17020
|
+
...options,
|
|
17021
|
+
latitude,
|
|
17022
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
17023
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
17024
|
+
});
|
|
17025
|
+
}
|
|
17026
|
+
}
|
|
17027
|
+
}
|
|
17028
|
+
/**
|
|
17029
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
17030
|
+
*
|
|
17031
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17032
|
+
*/
|
|
17033
|
+
function drawBlobbySkinSpots(options) {
|
|
17034
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
17035
|
+
for (const skinSpot of skinSpots) {
|
|
17036
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
17037
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17038
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
17039
|
+
continue;
|
|
17040
|
+
}
|
|
17041
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17042
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
17043
|
+
context.save();
|
|
17044
|
+
context.beginPath();
|
|
17045
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
17046
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
17047
|
+
context.fill();
|
|
17048
|
+
context.restore();
|
|
17049
|
+
}
|
|
17050
|
+
}
|
|
17051
|
+
/**
|
|
17052
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
17053
|
+
*
|
|
17054
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17055
|
+
*/
|
|
17056
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
17057
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
17058
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
17059
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
17060
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
17061
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17062
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
17063
|
+
return;
|
|
17064
|
+
}
|
|
17065
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17066
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17067
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17068
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
17069
|
+
radiusScale *
|
|
17070
|
+
0.78, size * 0.003, size * 0.02);
|
|
17071
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
17072
|
+
radiusScale *
|
|
17073
|
+
0.54, size * 0.0024, size * 0.015);
|
|
17074
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
17075
|
+
context.save();
|
|
17076
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
17077
|
+
context.rotate(rotation);
|
|
17078
|
+
context.beginPath();
|
|
17079
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
17080
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
17081
|
+
context.fill();
|
|
17082
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
17083
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
17084
|
+
context.stroke();
|
|
17085
|
+
context.restore();
|
|
17086
|
+
}
|
|
17087
|
+
/**
|
|
17088
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
17089
|
+
*
|
|
17090
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17091
|
+
*/
|
|
17092
|
+
function drawBlobbyContinuousGloss(options) {
|
|
17093
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
17094
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
17095
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
17096
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
17097
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
17098
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17099
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
17100
|
+
return;
|
|
17101
|
+
}
|
|
17102
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17103
|
+
const glossRadius = size * 0.058;
|
|
17104
|
+
context.save();
|
|
17105
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
17106
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
17107
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
17108
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
17109
|
+
context.fillStyle = glossGradient;
|
|
17110
|
+
context.beginPath();
|
|
17111
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
17112
|
+
context.fill();
|
|
17113
|
+
context.restore();
|
|
17114
|
+
}
|
|
17115
|
+
/**
|
|
17116
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
17117
|
+
*
|
|
17118
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17119
|
+
*/
|
|
17120
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
17121
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
17122
|
+
}
|
|
17123
|
+
/**
|
|
17124
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
17125
|
+
*
|
|
17126
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17127
|
+
*/
|
|
17128
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
17129
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
17130
|
+
return progress * progress * (3 - 2 * progress);
|
|
17131
|
+
}
|
|
17132
|
+
/**
|
|
17133
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
17134
|
+
*
|
|
17135
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17136
|
+
*/
|
|
17137
|
+
function formatAlphaHex(opacity) {
|
|
17138
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
17139
|
+
.toString(16)
|
|
17140
|
+
.padStart(2, '0');
|
|
17141
|
+
}
|
|
17142
|
+
|
|
15592
17143
|
/* eslint-disable no-magic-numbers */
|
|
15593
17144
|
/**
|
|
15594
17145
|
* Octopus avatar visual.
|
|
@@ -16359,6 +17910,8 @@
|
|
|
16359
17910
|
octopus3AvatarVisual,
|
|
16360
17911
|
octopus3dAvatarVisual,
|
|
16361
17912
|
octopus3d2AvatarVisual,
|
|
17913
|
+
octopus3d3AvatarVisual,
|
|
17914
|
+
octopus3d4AvatarVisual,
|
|
16362
17915
|
asciiOctopusAvatarVisual,
|
|
16363
17916
|
minecraftAvatarVisual,
|
|
16364
17917
|
minecraft2AvatarVisual,
|
|
@@ -18647,7 +20200,15 @@
|
|
|
18647
20200
|
* Builds the teammate request text, optionally including context.
|
|
18648
20201
|
*/
|
|
18649
20202
|
function buildTeammateRequest(message, context) {
|
|
18650
|
-
|
|
20203
|
+
if (!context) {
|
|
20204
|
+
return message;
|
|
20205
|
+
}
|
|
20206
|
+
return spacetrim.spaceTrim((block) => `
|
|
20207
|
+
${block(message)}
|
|
20208
|
+
|
|
20209
|
+
Context:
|
|
20210
|
+
${block(context)}
|
|
20211
|
+
`);
|
|
18651
20212
|
}
|
|
18652
20213
|
/**
|
|
18653
20214
|
* Builds a minimal chat prompt for teammate calls.
|
|
@@ -22473,7 +24034,11 @@
|
|
|
22473
24034
|
const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
|
|
22474
24035
|
const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
|
|
22475
24036
|
const contentToReturn = wasCharacterTruncated
|
|
22476
|
-
?
|
|
24037
|
+
? spacetrim.spaceTrim((block) => `
|
|
24038
|
+
${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
|
|
24039
|
+
|
|
24040
|
+
[...truncated...]
|
|
24041
|
+
`)
|
|
22477
24042
|
: lineRangedContent;
|
|
22478
24043
|
const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
|
|
22479
24044
|
if (wasCharacterTruncated) {
|
|
@@ -26009,11 +27574,19 @@
|
|
|
26009
27574
|
try {
|
|
26010
27575
|
const json = JSON.parse(content);
|
|
26011
27576
|
const formattedJson = JSON.stringify(json, null, 4);
|
|
26012
|
-
return
|
|
27577
|
+
return spacetrim.spaceTrim((block) => `
|
|
27578
|
+
\`\`\`json
|
|
27579
|
+
${block(formattedJson)}
|
|
27580
|
+
\`\`\`
|
|
27581
|
+
`);
|
|
26013
27582
|
}
|
|
26014
27583
|
catch (error) {
|
|
26015
27584
|
// If JSON is invalid, still import it but maybe not as pretty JSON
|
|
26016
|
-
return
|
|
27585
|
+
return spacetrim.spaceTrim((block) => `
|
|
27586
|
+
\`\`\`json
|
|
27587
|
+
${block(content)}
|
|
27588
|
+
\`\`\`
|
|
27589
|
+
`);
|
|
26017
27590
|
}
|
|
26018
27591
|
},
|
|
26019
27592
|
};
|
|
@@ -26037,7 +27610,11 @@
|
|
|
26037
27610
|
import(content, mimeType) {
|
|
26038
27611
|
const extension = mimeTypeToExtension(mimeType);
|
|
26039
27612
|
const codeBlockType = extension || 'txt';
|
|
26040
|
-
return
|
|
27613
|
+
return spacetrim.spaceTrim((block) => `
|
|
27614
|
+
\`\`\`${codeBlockType}
|
|
27615
|
+
${block(content)}
|
|
27616
|
+
\`\`\`
|
|
27617
|
+
`);
|
|
26041
27618
|
},
|
|
26042
27619
|
};
|
|
26043
27620
|
|
|
@@ -26284,11 +27861,19 @@
|
|
|
26284
27861
|
const examples = [];
|
|
26285
27862
|
const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
|
|
26286
27863
|
if (initialMessage) {
|
|
26287
|
-
examples.push(
|
|
27864
|
+
examples.push(spacetrim.spaceTrim((block) => `
|
|
27865
|
+
**Agent:**
|
|
27866
|
+
${block(initialMessage)}
|
|
27867
|
+
`));
|
|
26288
27868
|
}
|
|
26289
27869
|
if (samples && samples.length > 0) {
|
|
26290
27870
|
for (const sample of samples) {
|
|
26291
|
-
examples.push(
|
|
27871
|
+
examples.push(spacetrim.spaceTrim((block) => `
|
|
27872
|
+
**User:** ${block(String(sample.question))}
|
|
27873
|
+
|
|
27874
|
+
**Agent:**
|
|
27875
|
+
${block(sample.answer)}
|
|
27876
|
+
`));
|
|
26292
27877
|
}
|
|
26293
27878
|
}
|
|
26294
27879
|
return examples;
|
|
@@ -28003,7 +29588,11 @@
|
|
|
28003
29588
|
* @private internal utility of `createStandaloneBookLanguageMarkdown`
|
|
28004
29589
|
*/
|
|
28005
29590
|
function renderDocumentationSection(title, documentation) {
|
|
28006
|
-
return
|
|
29591
|
+
return spacetrim.spaceTrim((block) => `
|
|
29592
|
+
#### ${title}
|
|
29593
|
+
|
|
29594
|
+
${block(removeLeadingTopLevelHeading(documentation))}
|
|
29595
|
+
`);
|
|
28007
29596
|
}
|
|
28008
29597
|
/**
|
|
28009
29598
|
* Renders the documentation body for one grouped commitment entry.
|
|
@@ -28654,7 +30243,9 @@
|
|
|
28654
30243
|
* @private internal utility of `Book`
|
|
28655
30244
|
*/
|
|
28656
30245
|
function parseCommitmentHeader(line) {
|
|
28657
|
-
|
|
30246
|
+
// Require at least 2 characters in the first keyword word to avoid treating common
|
|
30247
|
+
// single-letter words (e.g. "V" in Czech, "I" or "A" in English) as commitment headers.
|
|
30248
|
+
const match = /^([A-Z][A-Z0-9]+(?: [A-Z0-9]+)*)(?:\s+(.*))?$/u.exec(line);
|
|
28658
30249
|
if (!match) {
|
|
28659
30250
|
return null;
|
|
28660
30251
|
}
|
|
@@ -28850,6 +30441,18 @@
|
|
|
28850
30441
|
const normalizedVersionName = versionName.trim();
|
|
28851
30442
|
return normalizedVersionName.length > 0 ? normalizedVersionName : null;
|
|
28852
30443
|
}
|
|
30444
|
+
/**
|
|
30445
|
+
* Builds a Supabase `.or()` filter for agent name or permanent id lookups.
|
|
30446
|
+
*
|
|
30447
|
+
* @param agentNameOrPermanentId - Agent name or stable permanent identifier to match.
|
|
30448
|
+
* @returns `.or()` filter string safe to pass to Supabase.
|
|
30449
|
+
*
|
|
30450
|
+
* @private internal helper of `AgentCollectionInSupabase`
|
|
30451
|
+
*/
|
|
30452
|
+
function buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId) {
|
|
30453
|
+
const encodedAgentIdentifier = encodeURIComponent(agentNameOrPermanentId);
|
|
30454
|
+
return `agentName.eq.${encodedAgentIdentifier},permanentId.eq.${encodedAgentIdentifier}`;
|
|
30455
|
+
}
|
|
28853
30456
|
/**
|
|
28854
30457
|
* Agent collection stored in a Supabase table.
|
|
28855
30458
|
*
|
|
@@ -28897,21 +30500,63 @@
|
|
|
28897
30500
|
if (isVerbose) {
|
|
28898
30501
|
console.info(`Found ${selectResult.data.length} agents in directory`);
|
|
28899
30502
|
}
|
|
28900
|
-
return selectResult.data.map((
|
|
28901
|
-
|
|
28902
|
-
|
|
30503
|
+
return selectResult.data.map((row) => this.mapAgentBasicInformationRow(row, isVerbose));
|
|
30504
|
+
}
|
|
30505
|
+
/**
|
|
30506
|
+
* Finds one active agent profile by its human-readable name or permanent id.
|
|
30507
|
+
*
|
|
30508
|
+
* This keeps route-level lookups from loading the whole agent collection when only
|
|
30509
|
+
* one canonical route identifier is needed.
|
|
30510
|
+
*
|
|
30511
|
+
* @param agentNameOrPermanentId - Agent name or stable permanent identifier.
|
|
30512
|
+
* @returns Matching active agent profile or `null` when not found.
|
|
30513
|
+
*
|
|
30514
|
+
* @public exported from `@promptbook/core`
|
|
30515
|
+
*/
|
|
30516
|
+
async findAgentBasicInformation(agentNameOrPermanentId) {
|
|
30517
|
+
var _a;
|
|
30518
|
+
const { isVerbose = exports.DEFAULT_IS_VERBOSE } = this.options || {};
|
|
30519
|
+
const selectResult = await this.supabaseClient
|
|
30520
|
+
.from(this.getTableName('Agent'))
|
|
30521
|
+
.select('agentName,agentProfile,permanentId')
|
|
30522
|
+
.or(buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId))
|
|
30523
|
+
.is('deletedAt', null)
|
|
30524
|
+
.order('createdAt', { ascending: true })
|
|
30525
|
+
.limit(1);
|
|
30526
|
+
if (selectResult.error) {
|
|
30527
|
+
throw new DatabaseError(spaceTrim((block) => `
|
|
30528
|
+
|
|
30529
|
+
Error fetching agent "${agentNameOrPermanentId}" from Supabase:
|
|
30530
|
+
|
|
30531
|
+
${block(selectResult.error.message)}
|
|
30532
|
+
`));
|
|
30533
|
+
}
|
|
30534
|
+
const row = (_a = selectResult.data) === null || _a === void 0 ? void 0 : _a[0];
|
|
30535
|
+
return row ? this.mapAgentBasicInformationRow(row, isVerbose) : null;
|
|
30536
|
+
}
|
|
30537
|
+
/**
|
|
30538
|
+
* Converts one database row into public agent information.
|
|
30539
|
+
*
|
|
30540
|
+
* @param row - Database row carrying the persisted profile snapshot.
|
|
30541
|
+
* @param isVerbose - Whether profile-name mismatches should be logged.
|
|
30542
|
+
* @returns Agent profile with canonical database name and permanent id.
|
|
30543
|
+
*
|
|
30544
|
+
* @private internal helper of `AgentCollectionInSupabase`
|
|
30545
|
+
*/
|
|
30546
|
+
mapAgentBasicInformationRow({ agentName, agentProfile, permanentId }, isVerbose) {
|
|
30547
|
+
if (isVerbose && agentProfile.agentName !== agentName) {
|
|
30548
|
+
console.warn(spaceTrim(`
|
|
28903
30549
|
Agent name mismatch for agent "${agentName}". Using name from database.
|
|
28904
30550
|
|
|
28905
30551
|
agentName: "${agentName}"
|
|
28906
30552
|
agentProfile.agentName: "${agentProfile.agentName}"
|
|
28907
30553
|
`));
|
|
28908
|
-
|
|
28909
|
-
|
|
28910
|
-
|
|
28911
|
-
|
|
28912
|
-
|
|
28913
|
-
|
|
28914
|
-
});
|
|
30554
|
+
}
|
|
30555
|
+
return {
|
|
30556
|
+
...agentProfile,
|
|
30557
|
+
agentName,
|
|
30558
|
+
permanentId: permanentId || agentProfile.permanentId,
|
|
30559
|
+
};
|
|
28915
30560
|
}
|
|
28916
30561
|
/**
|
|
28917
30562
|
* Retrieves the permanent ID of an agent by its name or permanent ID.
|
|
@@ -28920,7 +30565,7 @@
|
|
|
28920
30565
|
const selectResult = await this.supabaseClient
|
|
28921
30566
|
.from(this.getTableName('Agent'))
|
|
28922
30567
|
.select('permanentId')
|
|
28923
|
-
.or(
|
|
30568
|
+
.or(buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId))
|
|
28924
30569
|
.order('createdAt', { ascending: true }) // Pick oldest if multiple match by name
|
|
28925
30570
|
.limit(1);
|
|
28926
30571
|
if (selectResult.error || !selectResult.data || selectResult.data.length === 0) {
|
|
@@ -28935,7 +30580,7 @@
|
|
|
28935
30580
|
const selectResult = await this.supabaseClient
|
|
28936
30581
|
.from(this.getTableName('Agent'))
|
|
28937
30582
|
.select('agentSource')
|
|
28938
|
-
.or(
|
|
30583
|
+
.or(buildAgentNameOrPermanentIdFilter(agentNameOrPermanentId))
|
|
28939
30584
|
.is('deletedAt', null)
|
|
28940
30585
|
.order('createdAt', { ascending: true }) // Pick oldest if multiple match by name
|
|
28941
30586
|
.limit(1);
|
|
@@ -34416,7 +36061,8 @@
|
|
|
34416
36061
|
* @private internal function of `$registeredLlmToolsMessage`
|
|
34417
36062
|
*/
|
|
34418
36063
|
function createUsedEnvMessage() {
|
|
34419
|
-
return `Unknown \`.env\` file`
|
|
36064
|
+
return `Unknown \`.env\` file`
|
|
36065
|
+
;
|
|
34420
36066
|
}
|
|
34421
36067
|
// TODO: [®] DRY Register logic
|
|
34422
36068
|
// TODO: [🧠][⚛] Maybe pass env as argument
|
|
@@ -35729,7 +37375,9 @@
|
|
|
35729
37375
|
if (!executionTools || !executionTools.script) {
|
|
35730
37376
|
throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
|
|
35731
37377
|
}
|
|
35732
|
-
return Array.isArray(executionTools.script)
|
|
37378
|
+
return Array.isArray(executionTools.script)
|
|
37379
|
+
? executionTools.script
|
|
37380
|
+
: [executionTools.script];
|
|
35733
37381
|
}
|
|
35734
37382
|
/**
|
|
35735
37383
|
* Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
|
|
@@ -38791,7 +40439,7 @@
|
|
|
38791
40439
|
return { skippedReason: 'invalid_data_url' };
|
|
38792
40440
|
}
|
|
38793
40441
|
return {
|
|
38794
|
-
file: new File([parsed.buffer], parsed.filename, {
|
|
40442
|
+
file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
|
|
38795
40443
|
type: parsed.mimeType,
|
|
38796
40444
|
}),
|
|
38797
40445
|
sizeBytes: parsed.buffer.length,
|
|
@@ -40554,7 +42202,7 @@
|
|
|
40554
42202
|
* Computes one stable hash from a JSON-serializable value.
|
|
40555
42203
|
*/
|
|
40556
42204
|
function computeJsonHash$1(value) {
|
|
40557
|
-
return
|
|
42205
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
40558
42206
|
}
|
|
40559
42207
|
/**
|
|
40560
42208
|
* Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
|
|
@@ -40712,7 +42360,7 @@
|
|
|
40712
42360
|
* Computes one stable hash from a JSON-serializable value.
|
|
40713
42361
|
*/
|
|
40714
42362
|
function computeJsonHash(value) {
|
|
40715
|
-
return
|
|
42363
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
40716
42364
|
}
|
|
40717
42365
|
/**
|
|
40718
42366
|
* Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
|
|
@@ -41102,7 +42750,11 @@
|
|
|
41102
42750
|
};
|
|
41103
42751
|
}
|
|
41104
42752
|
return {
|
|
41105
|
-
content:
|
|
42753
|
+
content: spacetrim.spaceTrim((block) => `
|
|
42754
|
+
${block(content.slice(0, Math.max(0, maxCharacters)))}
|
|
42755
|
+
|
|
42756
|
+
[...truncated...]
|
|
42757
|
+
`),
|
|
41106
42758
|
isTruncated: true,
|
|
41107
42759
|
};
|
|
41108
42760
|
}
|
|
@@ -41568,7 +43220,7 @@
|
|
|
41568
43220
|
* Returns a virtual model name representing the agent behavior.
|
|
41569
43221
|
*/
|
|
41570
43222
|
get modelName() {
|
|
41571
|
-
const hash =
|
|
43223
|
+
const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
|
|
41572
43224
|
const agentId = hash.substring(0, 10);
|
|
41573
43225
|
return (normalizeToKebabCase(this.title) + '-' + agentId);
|
|
41574
43226
|
}
|
|
@@ -45279,7 +46931,7 @@
|
|
|
45279
46931
|
title: 'Anthropic Claude',
|
|
45280
46932
|
packageName: '@promptbook/anthropic-claude',
|
|
45281
46933
|
className: 'AnthropicClaudeExecutionTools',
|
|
45282
|
-
envVariables: ['ANTHROPIC_CLAUDE_API_KEY'],
|
|
46934
|
+
envVariables: ['ANTHROPIC_API_KEY', 'ANTHROPIC_CLAUDE_API_KEY'],
|
|
45283
46935
|
trustLevel: 'CLOSED',
|
|
45284
46936
|
order: MODEL_ORDERS.TOP_TIER,
|
|
45285
46937
|
getBoilerplateConfiguration() {
|
|
@@ -45296,14 +46948,17 @@
|
|
|
45296
46948
|
};
|
|
45297
46949
|
},
|
|
45298
46950
|
createConfigurationFromEnv(env) {
|
|
46951
|
+
var _a;
|
|
45299
46952
|
// Note: Note using `process.env` BUT `env` to pass in the environment variables dynamically
|
|
45300
|
-
|
|
46953
|
+
// Note: `ANTHROPIC_API_KEY` takes precedence over the deprecated `ANTHROPIC_CLAUDE_API_KEY`
|
|
46954
|
+
const apiKey = (_a = env.ANTHROPIC_API_KEY) !== null && _a !== void 0 ? _a : env.ANTHROPIC_CLAUDE_API_KEY;
|
|
46955
|
+
if (typeof apiKey === 'string') {
|
|
45301
46956
|
return {
|
|
45302
46957
|
title: 'Claude (from env)',
|
|
45303
46958
|
packageName: '@promptbook/anthropic-claude',
|
|
45304
46959
|
className: 'AnthropicClaudeExecutionTools',
|
|
45305
46960
|
options: {
|
|
45306
|
-
apiKey
|
|
46961
|
+
apiKey,
|
|
45307
46962
|
},
|
|
45308
46963
|
};
|
|
45309
46964
|
}
|
|
@@ -48714,20 +50369,7 @@
|
|
|
48714
50369
|
}
|
|
48715
50370
|
|
|
48716
50371
|
/**
|
|
48717
|
-
*
|
|
48718
|
-
*
|
|
48719
|
-
* Used for "Adam" agent which is built in as default ancestor for new agents and other well known agents
|
|
48720
|
-
*
|
|
48721
|
-
* @public exported from `@promptbook/core`
|
|
48722
|
-
*/
|
|
48723
|
-
const CORE_AGENTS_SERVER = {
|
|
48724
|
-
title: 'Promptbook Core',
|
|
48725
|
-
description: `Core Promptbook server used for Adam agent which is built in as default ancestor for new agents and other well known agents.`,
|
|
48726
|
-
owner: PROMPTBOOK_LEGAL_ENTITY,
|
|
48727
|
-
url: 'https://core.ptbk.io/',
|
|
48728
|
-
};
|
|
48729
|
-
/**
|
|
48730
|
-
* Names of well known agents hosted on Core Promptbook server
|
|
50372
|
+
* Names of well known agents bundled in the local `.core` folder of every Agents Server
|
|
48731
50373
|
*
|
|
48732
50374
|
* - `Adam`: The default ancestor agent for new agents
|
|
48733
50375
|
* - `Teacher`: Agent that knows book syntax and can help with self-learning
|
|
@@ -48753,7 +50395,6 @@
|
|
|
48753
50395
|
* @public exported from `@promptbook/core`
|
|
48754
50396
|
*/
|
|
48755
50397
|
const PUBLIC_AGENTS_SERVERS = [
|
|
48756
|
-
CORE_AGENTS_SERVER,
|
|
48757
50398
|
{
|
|
48758
50399
|
title: 'Promptbook Gallery',
|
|
48759
50400
|
description: `Gallery of ideas, AI professions,... like AI Agenta photobank.`,
|
|
@@ -49229,7 +50870,6 @@
|
|
|
49229
50870
|
exports.CLAIM = CLAIM;
|
|
49230
50871
|
exports.CLI_APP_ID = CLI_APP_ID;
|
|
49231
50872
|
exports.COLOR_CONSTANTS = COLOR_CONSTANTS;
|
|
49232
|
-
exports.CORE_AGENTS_SERVER = CORE_AGENTS_SERVER;
|
|
49233
50873
|
exports.CORE_AGENTS_SERVER_WELL_KNOWN_AGENT_NAMES = CORE_AGENTS_SERVER_WELL_KNOWN_AGENT_NAMES;
|
|
49234
50874
|
exports.CallbackInterfaceTools = CallbackInterfaceTools;
|
|
49235
50875
|
exports.ChatbotFormfactorDefinition = ChatbotFormfactorDefinition;
|