@promptbook/remote-server 0.112.0-99 → 0.113.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -44
- package/esm/index.es.js +1801 -98
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/components.index.d.ts +4 -0
- package/esm/src/_packages/core.index.d.ts +22 -2
- package/esm/src/_packages/node.index.d.ts +40 -0
- package/esm/src/_packages/types.index.d.ts +20 -0
- package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/esm/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/esm/src/book-3.0/CliAgent.d.ts +66 -0
- package/esm/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/LiteAgent.d.ts +68 -0
- package/esm/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/esm/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/esm/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/esm/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
- package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/esm/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/esm/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/esm/src/cli/cli-commands/agent.d.ts +14 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
- package/esm/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/esm/src/commitments/index.d.ts +2 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/esm/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +1802 -96
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/components.index.d.ts +4 -0
- package/umd/src/_packages/core.index.d.ts +22 -2
- package/umd/src/_packages/node.index.d.ts +40 -0
- package/umd/src/_packages/types.index.d.ts +20 -0
- package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/umd/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/umd/src/book-3.0/CliAgent.d.ts +66 -0
- package/umd/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/LiteAgent.d.ts +68 -0
- package/umd/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/umd/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/umd/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/umd/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
- package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/umd/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/umd/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/umd/src/cli/cli-commands/agent.d.ts +14 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
- package/umd/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/umd/src/commitments/index.d.ts +2 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/umd/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -0
- package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
2
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('path'), require('child_process'), require('colors'), require('spacetrim'), require('waitasecond'), require('http'), require('express'), require('socket.io'), require('fs/promises'), require('@mozilla/readability'), require('jsdom'), require('crypto-js'), require('crypto-js/enc-hex'), require('showdown'), require('express-openapi-validator'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('rxjs'), require('crypto'), require('moment'), require('mime-types'), require('crypto-js/sha256'), require('papaparse'), require('@openai/agents'), require('bottleneck'), require('openai')) :
|
|
3
3
|
typeof define === 'function' && define.amd ? define(['exports', 'path', 'child_process', 'colors', 'spacetrim', 'waitasecond', 'http', 'express', 'socket.io', 'fs/promises', '@mozilla/readability', 'jsdom', 'crypto-js', 'crypto-js/enc-hex', 'showdown', 'express-openapi-validator', 'swagger-ui-express', 'react', 'react-dom/server', 'rxjs', 'crypto', 'moment', 'mime-types', 'crypto-js/sha256', 'papaparse', '@openai/agents', 'bottleneck', 'openai'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-remote-server"] = {}, global.path, global.child_process, global.colors, global._spaceTrim, global.waitasecond, global.http, global.express, global.socket_io, global.promises, global.readability, global.jsdom, global.
|
|
5
|
-
})(this, (function (exports, path, child_process, colors, _spaceTrim, waitasecond, http, express, socket_io, promises, readability, jsdom,
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-remote-server"] = {}, global.path, global.child_process, global.colors, global._spaceTrim, global.waitasecond, global.http, global.express, global.socket_io, global.promises, global.readability, global.jsdom, global.CryptoJS, global.hexEncoder, global.showdown, global.OpenApiValidator, global.swaggerUi, global.react, global.server, global.rxjs, null, global.moment, global.mimeTypes, global.sha256, global.papaparse, global.agents, global.Bottleneck, global.OpenAI));
|
|
5
|
+
})(this, (function (exports, path, child_process, colors, _spaceTrim, waitasecond, http, express, socket_io, promises, readability, jsdom, CryptoJS, hexEncoder, showdown, OpenApiValidator, swaggerUi, react, server, rxjs, crypto, moment, mimeTypes, sha256, papaparse, 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
|
|
|
@@ -28,11 +28,14 @@
|
|
|
28
28
|
var _spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(_spaceTrim);
|
|
29
29
|
var http__default = /*#__PURE__*/_interopDefaultLegacy(http);
|
|
30
30
|
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
|
|
31
|
+
var CryptoJS__default = /*#__PURE__*/_interopDefaultLegacy(CryptoJS);
|
|
31
32
|
var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
|
|
33
|
+
var showdown__default = /*#__PURE__*/_interopDefaultLegacy(showdown);
|
|
32
34
|
var OpenApiValidator__namespace = /*#__PURE__*/_interopNamespace(OpenApiValidator);
|
|
33
35
|
var swaggerUi__default = /*#__PURE__*/_interopDefaultLegacy(swaggerUi);
|
|
34
36
|
var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
|
|
35
37
|
var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
|
|
38
|
+
var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
|
|
36
39
|
var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
|
|
37
40
|
var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
|
|
38
41
|
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
* @generated
|
|
51
54
|
* @see https://github.com/webgptorg/promptbook
|
|
52
55
|
*/
|
|
53
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.
|
|
56
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.113.0-0';
|
|
54
57
|
/**
|
|
55
58
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
56
59
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1958,7 +1961,7 @@
|
|
|
1958
1961
|
* @private internal helper function
|
|
1959
1962
|
*/
|
|
1960
1963
|
function $randomToken(randomness) {
|
|
1961
|
-
return
|
|
1964
|
+
return CryptoJS__default["default"].lib.WordArray.random(randomness).toString(CryptoJS__default["default"].enc.Hex);
|
|
1962
1965
|
}
|
|
1963
1966
|
// TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
|
|
1964
1967
|
// TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
@@ -5502,7 +5505,7 @@
|
|
|
5502
5505
|
* @public exported from `@promptbook/editable`
|
|
5503
5506
|
*/
|
|
5504
5507
|
function knowledgeSourceContentToName(knowledgeSourceContent) {
|
|
5505
|
-
const hash =
|
|
5508
|
+
const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
|
|
5506
5509
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
5507
5510
|
.toString( /* hex */)
|
|
5508
5511
|
.substring(0, 20);
|
|
@@ -6496,7 +6499,7 @@
|
|
|
6496
6499
|
console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
|
|
6497
6500
|
value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
6498
6501
|
}
|
|
6499
|
-
const csv =
|
|
6502
|
+
const csv = papaparse__default["default"].parse(value, settings);
|
|
6500
6503
|
return csv;
|
|
6501
6504
|
}
|
|
6502
6505
|
|
|
@@ -6581,10 +6584,10 @@
|
|
|
6581
6584
|
i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
|
|
6582
6585
|
);
|
|
6583
6586
|
*/
|
|
6584
|
-
await onProgress(
|
|
6587
|
+
await onProgress(papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
|
|
6585
6588
|
}
|
|
6586
6589
|
}
|
|
6587
|
-
return
|
|
6590
|
+
return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6588
6591
|
},
|
|
6589
6592
|
},
|
|
6590
6593
|
{
|
|
@@ -6612,7 +6615,7 @@
|
|
|
6612
6615
|
return /* not await */ mapCallback({ [key]: value }, index, array.length);
|
|
6613
6616
|
}));
|
|
6614
6617
|
}));
|
|
6615
|
-
return
|
|
6618
|
+
return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
6616
6619
|
},
|
|
6617
6620
|
},
|
|
6618
6621
|
],
|
|
@@ -9269,7 +9272,8 @@
|
|
|
9269
9272
|
* @private internal function of `$registeredLlmToolsMessage`
|
|
9270
9273
|
*/
|
|
9271
9274
|
function createUsedEnvMessage() {
|
|
9272
|
-
return `Unknown \`.env\` file`
|
|
9275
|
+
return `Unknown \`.env\` file`
|
|
9276
|
+
;
|
|
9273
9277
|
}
|
|
9274
9278
|
// TODO: [®] DRY Register logic
|
|
9275
9279
|
// TODO: [🧠][⚛] Maybe pass env as argument
|
|
@@ -9541,7 +9545,7 @@
|
|
|
9541
9545
|
const { filename: sourceFilename, url } = source;
|
|
9542
9546
|
const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
|
|
9543
9547
|
// TODO: [👬] DRY
|
|
9544
|
-
const hash =
|
|
9548
|
+
const hash = CryptoJS__default["default"].SHA256(
|
|
9545
9549
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
9546
9550
|
hexEncoder__default["default"].parse(sourceFilename || url || 'untitled'))
|
|
9547
9551
|
.toString( /* hex */)
|
|
@@ -9632,7 +9636,7 @@
|
|
|
9632
9636
|
* @private utility of `WebsiteScraper`
|
|
9633
9637
|
*/
|
|
9634
9638
|
function createShowdownConverter() {
|
|
9635
|
-
return new
|
|
9639
|
+
return new showdown__default["default"].Converter({
|
|
9636
9640
|
flavor: 'github',
|
|
9637
9641
|
/*
|
|
9638
9642
|
> import showdownHighlight from 'showdown-highlight';
|
|
@@ -10824,7 +10828,7 @@
|
|
|
10824
10828
|
* @public exported from `@promptbook/utils`
|
|
10825
10829
|
*/
|
|
10826
10830
|
function computeHash(value) {
|
|
10827
|
-
return
|
|
10831
|
+
return CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(_spaceTrim.spaceTrim(valueToString(value)))).toString( /* hex */);
|
|
10828
10832
|
}
|
|
10829
10833
|
// TODO: [🥬][🥬] Use this ACRY
|
|
10830
10834
|
|
|
@@ -11066,6 +11070,11 @@
|
|
|
11066
11070
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
11067
11071
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
11068
11072
|
*
|
|
11073
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
11074
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
11075
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
11076
|
+
* outbound request.
|
|
11077
|
+
*
|
|
11069
11078
|
* @public exported from `@promptbook/utils`
|
|
11070
11079
|
*/
|
|
11071
11080
|
function isValidAgentUrl(url) {
|
|
@@ -11079,12 +11088,6 @@
|
|
|
11079
11088
|
// TODO: [🐠]
|
|
11080
11089
|
return false;
|
|
11081
11090
|
}
|
|
11082
|
-
/*
|
|
11083
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
11084
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
11085
|
-
return false;
|
|
11086
|
-
}
|
|
11087
|
-
*/
|
|
11088
11091
|
return true;
|
|
11089
11092
|
}
|
|
11090
11093
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -11260,8 +11263,11 @@
|
|
|
11260
11263
|
if (!trimmedContent) {
|
|
11261
11264
|
return requirements;
|
|
11262
11265
|
}
|
|
11263
|
-
|
|
11264
|
-
|
|
11266
|
+
const goalSection = _spaceTrim.spaceTrim((block) => `
|
|
11267
|
+
## Goal
|
|
11268
|
+
|
|
11269
|
+
${block(trimmedContent)}
|
|
11270
|
+
`);
|
|
11265
11271
|
const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
|
|
11266
11272
|
return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
|
|
11267
11273
|
}
|
|
@@ -11783,8 +11789,11 @@
|
|
|
11783
11789
|
if (!trimmedContent) {
|
|
11784
11790
|
return requirements;
|
|
11785
11791
|
}
|
|
11786
|
-
|
|
11787
|
-
|
|
11792
|
+
const languageSection = _spaceTrim.spaceTrim((block) => `
|
|
11793
|
+
## Language
|
|
11794
|
+
|
|
11795
|
+
- Your language is ${block(trimmedContent)}
|
|
11796
|
+
`);
|
|
11788
11797
|
return this.appendToSystemMessage(requirements, languageSection, '\n\n');
|
|
11789
11798
|
}
|
|
11790
11799
|
}
|
|
@@ -14250,21 +14259,22 @@
|
|
|
14250
14259
|
* @private helper of `fractalAvatarVisual`
|
|
14251
14260
|
*/
|
|
14252
14261
|
function drawDragonCurveLayer(context, points, options) {
|
|
14253
|
-
const {
|
|
14262
|
+
const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
|
|
14254
14263
|
const firstPoint = points[0];
|
|
14255
14264
|
const lastPoint = points[points.length - 1];
|
|
14256
14265
|
const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
|
|
14257
14266
|
ribbonGradient.addColorStop(0, `${primaryColor}f2`);
|
|
14258
14267
|
ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
|
|
14259
14268
|
ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
|
|
14269
|
+
// Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
|
|
14270
|
+
// context.filter blur, which triggers a costly software rasterization pass every frame.
|
|
14260
14271
|
context.save();
|
|
14261
14272
|
context.beginPath();
|
|
14262
14273
|
tracePolyline(context, points);
|
|
14263
|
-
context.strokeStyle = `${shadowColor}
|
|
14264
|
-
context.lineWidth = strokeWidth *
|
|
14274
|
+
context.strokeStyle = `${shadowColor}48`;
|
|
14275
|
+
context.lineWidth = strokeWidth * 4.5;
|
|
14265
14276
|
context.lineJoin = 'round';
|
|
14266
14277
|
context.lineCap = 'round';
|
|
14267
|
-
context.filter = `blur(${size * 0.022}px)`;
|
|
14268
14278
|
context.stroke();
|
|
14269
14279
|
context.restore();
|
|
14270
14280
|
context.beginPath();
|
|
@@ -14726,7 +14736,7 @@
|
|
|
14726
14736
|
*
|
|
14727
14737
|
* @private helper of `minecraft2AvatarVisual`
|
|
14728
14738
|
*/
|
|
14729
|
-
const LIGHT_DIRECTION$
|
|
14739
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
14730
14740
|
x: 0.4,
|
|
14731
14741
|
y: -0.65,
|
|
14732
14742
|
z: 0.92,
|
|
@@ -14848,11 +14858,23 @@
|
|
|
14848
14858
|
* @private helper of `minecraft2AvatarVisual`
|
|
14849
14859
|
*/
|
|
14850
14860
|
function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
|
|
14861
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.03;
|
|
14862
|
+
const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
|
|
14863
|
+
const rx = size * (0.16 + interaction.intensity * 0.015);
|
|
14864
|
+
const ry = size * 0.055;
|
|
14865
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
14851
14866
|
context.save();
|
|
14852
|
-
context.
|
|
14853
|
-
context.
|
|
14867
|
+
context.translate(cx, cy);
|
|
14868
|
+
context.scale(1, ry / rx);
|
|
14869
|
+
const blurRadius = rx * 1.4;
|
|
14870
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
14871
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
14872
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
14873
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
14874
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
14875
|
+
context.fillStyle = shadowGradient;
|
|
14854
14876
|
context.beginPath();
|
|
14855
|
-
context.
|
|
14877
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
14856
14878
|
context.fill();
|
|
14857
14879
|
context.restore();
|
|
14858
14880
|
}
|
|
@@ -14938,7 +14960,7 @@
|
|
|
14938
14960
|
corners: projectedCorners,
|
|
14939
14961
|
texture: faceDefinition.texture,
|
|
14940
14962
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
14941
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
14963
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
14942
14964
|
outlineColor: cuboid.outlineColor,
|
|
14943
14965
|
};
|
|
14944
14966
|
});
|
|
@@ -15076,13 +15098,27 @@
|
|
|
15076
15098
|
spotlight.addColorStop(1, `${palette.highlight}00`);
|
|
15077
15099
|
context.fillStyle = spotlight;
|
|
15078
15100
|
context.fillRect(0, 0, size, size);
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15101
|
+
{
|
|
15102
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
15103
|
+
const cx = size * 0.5;
|
|
15104
|
+
const cy = size * 0.86;
|
|
15105
|
+
const rx = size * 0.2;
|
|
15106
|
+
const ry = size * 0.06;
|
|
15107
|
+
const blurRadius = rx * 1.4;
|
|
15108
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
15109
|
+
shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
|
|
15110
|
+
shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
|
|
15111
|
+
shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
|
|
15112
|
+
shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
15113
|
+
context.save();
|
|
15114
|
+
context.translate(cx, cy);
|
|
15115
|
+
context.scale(1, ry / rx);
|
|
15116
|
+
context.fillStyle = shadowGradient;
|
|
15117
|
+
context.beginPath();
|
|
15118
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
15119
|
+
context.fill();
|
|
15120
|
+
context.restore();
|
|
15121
|
+
}
|
|
15086
15122
|
drawVoxelCuboid(context, {
|
|
15087
15123
|
x: bodyX,
|
|
15088
15124
|
y: bodyY,
|
|
@@ -15611,7 +15647,7 @@
|
|
|
15611
15647
|
*
|
|
15612
15648
|
* @private helper of `octopus3AvatarVisual`
|
|
15613
15649
|
*/
|
|
15614
|
-
function formatAlphaHex$
|
|
15650
|
+
function formatAlphaHex$2(opacity) {
|
|
15615
15651
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
15616
15652
|
.toString(16)
|
|
15617
15653
|
.padStart(2, '0');
|
|
@@ -15990,7 +16026,7 @@
|
|
|
15990
16026
|
context.beginPath();
|
|
15991
16027
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
15992
16028
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
15993
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
16029
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
15994
16030
|
context.lineWidth = radiusX * 0.08;
|
|
15995
16031
|
context.lineCap = 'round';
|
|
15996
16032
|
context.stroke();
|
|
@@ -16071,7 +16107,7 @@
|
|
|
16071
16107
|
context.beginPath();
|
|
16072
16108
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16073
16109
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
16074
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
16110
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
16075
16111
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
16076
16112
|
context.lineCap = 'round';
|
|
16077
16113
|
context.stroke();
|
|
@@ -16117,7 +16153,7 @@
|
|
|
16117
16153
|
*
|
|
16118
16154
|
* @private helper of the 3D octopus avatar visuals
|
|
16119
16155
|
*/
|
|
16120
|
-
function formatAlphaHex(opacity) {
|
|
16156
|
+
function formatAlphaHex$1(opacity) {
|
|
16121
16157
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
16122
16158
|
.toString(16)
|
|
16123
16159
|
.padStart(2, '0');
|
|
@@ -16129,11 +16165,40 @@
|
|
|
16129
16165
|
*
|
|
16130
16166
|
* @private helper of `octopus3dAvatarVisual`
|
|
16131
16167
|
*/
|
|
16132
|
-
const LIGHT_DIRECTION$
|
|
16168
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
16133
16169
|
x: 0.48,
|
|
16134
16170
|
y: -0.62,
|
|
16135
16171
|
z: 0.94,
|
|
16136
16172
|
});
|
|
16173
|
+
/**
|
|
16174
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
16175
|
+
*
|
|
16176
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
16177
|
+
*/
|
|
16178
|
+
const octopus3dStableStateCache = new WeakMap();
|
|
16179
|
+
/**
|
|
16180
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
16181
|
+
*
|
|
16182
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
16183
|
+
*/
|
|
16184
|
+
function getOctopus3dStableState(createRandom) {
|
|
16185
|
+
const cached = octopus3dStableStateCache.get(createRandom);
|
|
16186
|
+
if (cached !== undefined) {
|
|
16187
|
+
return cached;
|
|
16188
|
+
}
|
|
16189
|
+
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
16190
|
+
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
16191
|
+
const leftEyePhaseOffset = eyeRandom() * 0.6;
|
|
16192
|
+
const rightEyePhaseOffset = eyeRandom() * 0.6;
|
|
16193
|
+
const state = {
|
|
16194
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
16195
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16196
|
+
leftEyePhaseOffset,
|
|
16197
|
+
rightEyePhaseOffset,
|
|
16198
|
+
};
|
|
16199
|
+
octopus3dStableStateCache.set(createRandom, state);
|
|
16200
|
+
return state;
|
|
16201
|
+
}
|
|
16137
16202
|
/**
|
|
16138
16203
|
* Proper 3D Octopus visual built from projected organic meshes and tentacles.
|
|
16139
16204
|
*
|
|
@@ -16146,10 +16211,7 @@
|
|
|
16146
16211
|
isAnimated: true,
|
|
16147
16212
|
supportsPointerTracking: true,
|
|
16148
16213
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
16149
|
-
const morphologyProfile =
|
|
16150
|
-
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
16151
|
-
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
16152
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
16214
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
|
|
16153
16215
|
const sceneCenterX = size * 0.5;
|
|
16154
16216
|
const sceneCenterY = size * 0.56;
|
|
16155
16217
|
const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
|
|
@@ -16246,12 +16308,12 @@
|
|
|
16246
16308
|
x: -faceEyeSpacing,
|
|
16247
16309
|
y: faceEyeYOffset,
|
|
16248
16310
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
|
|
16249
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
16311
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16250
16312
|
drawProjectedOrganicEye(context, {
|
|
16251
16313
|
x: faceEyeSpacing,
|
|
16252
16314
|
y: faceEyeYOffset,
|
|
16253
16315
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
|
|
16254
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 +
|
|
16316
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16255
16317
|
drawProjectedOrganicMouth(context, [
|
|
16256
16318
|
{
|
|
16257
16319
|
x: -mouthHalfWidth,
|
|
@@ -16295,14 +16357,28 @@
|
|
|
16295
16357
|
/**
|
|
16296
16358
|
* Draws the soft ground shadow below the octopus.
|
|
16297
16359
|
*
|
|
16360
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
16361
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
16362
|
+
*
|
|
16298
16363
|
* @private helper of `octopus3dAvatarVisual`
|
|
16299
16364
|
*/
|
|
16300
16365
|
function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
|
|
16366
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.04;
|
|
16367
|
+
const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
|
|
16368
|
+
const rx = size * (0.18 + interaction.intensity * 0.02);
|
|
16369
|
+
const ry = size * 0.06;
|
|
16301
16370
|
context.save();
|
|
16302
|
-
context.
|
|
16303
|
-
context.
|
|
16371
|
+
context.translate(cx, cy);
|
|
16372
|
+
context.scale(1, ry / rx);
|
|
16373
|
+
const blurRadius = rx * 1.4;
|
|
16374
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
16375
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
16376
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
16377
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
16378
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
16379
|
+
context.fillStyle = shadowGradient;
|
|
16304
16380
|
context.beginPath();
|
|
16305
|
-
context.
|
|
16381
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
16306
16382
|
context.fill();
|
|
16307
16383
|
context.restore();
|
|
16308
16384
|
}
|
|
@@ -16339,7 +16415,7 @@
|
|
|
16339
16415
|
corners: projectedCorners,
|
|
16340
16416
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
16341
16417
|
transformedCorners.length,
|
|
16342
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
16418
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
16343
16419
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
16344
16420
|
outlineColor,
|
|
16345
16421
|
});
|
|
@@ -16528,11 +16604,40 @@
|
|
|
16528
16604
|
*
|
|
16529
16605
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16530
16606
|
*/
|
|
16531
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
16607
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
16532
16608
|
x: 0.38,
|
|
16533
16609
|
y: -0.6,
|
|
16534
16610
|
z: 0.98,
|
|
16535
16611
|
});
|
|
16612
|
+
/**
|
|
16613
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
16614
|
+
*
|
|
16615
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16616
|
+
*/
|
|
16617
|
+
const octopus3d2StableStateCache = new WeakMap();
|
|
16618
|
+
/**
|
|
16619
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
16620
|
+
*
|
|
16621
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16622
|
+
*/
|
|
16623
|
+
function getOctopus3d2StableState(createRandom) {
|
|
16624
|
+
const cached = octopus3d2StableStateCache.get(createRandom);
|
|
16625
|
+
if (cached !== undefined) {
|
|
16626
|
+
return cached;
|
|
16627
|
+
}
|
|
16628
|
+
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
16629
|
+
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
16630
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
16631
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
16632
|
+
const state = {
|
|
16633
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
16634
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16635
|
+
leftEyePhaseOffset,
|
|
16636
|
+
rightEyePhaseOffset,
|
|
16637
|
+
};
|
|
16638
|
+
octopus3d2StableStateCache.set(createRandom, state);
|
|
16639
|
+
return state;
|
|
16640
|
+
}
|
|
16536
16641
|
/**
|
|
16537
16642
|
* Octopus 3D 2 avatar visual.
|
|
16538
16643
|
*
|
|
@@ -16545,10 +16650,7 @@
|
|
|
16545
16650
|
isAnimated: true,
|
|
16546
16651
|
supportsPointerTracking: true,
|
|
16547
16652
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
16548
|
-
const morphologyProfile =
|
|
16549
|
-
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
16550
|
-
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
16551
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
16653
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
|
|
16552
16654
|
const sceneCenterX = size * 0.5;
|
|
16553
16655
|
const sceneCenterY = size * 0.575;
|
|
16554
16656
|
const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
|
|
@@ -16601,8 +16703,8 @@
|
|
|
16601
16703
|
const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
|
|
16602
16704
|
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
16603
16705
|
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
16604
|
-
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
16605
|
-
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 +
|
|
16706
|
+
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16707
|
+
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
16606
16708
|
drawProjectedOrganicMouth(context, [
|
|
16607
16709
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
16608
16710
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
@@ -16631,54 +16733,118 @@
|
|
|
16631
16733
|
/**
|
|
16632
16734
|
* Draws the soft floor shadow that anchors the single mesh in the frame.
|
|
16633
16735
|
*
|
|
16736
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
16737
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
16738
|
+
*
|
|
16634
16739
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16635
16740
|
*/
|
|
16636
16741
|
function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
16742
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
16743
|
+
const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
|
|
16744
|
+
const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
|
|
16745
|
+
const ry = size * 0.062;
|
|
16637
16746
|
context.save();
|
|
16638
|
-
context.
|
|
16639
|
-
context.
|
|
16747
|
+
context.translate(cx, cy);
|
|
16748
|
+
context.scale(1, ry / rx);
|
|
16749
|
+
const blurRadius = rx * 1.4;
|
|
16750
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
16751
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
16752
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
16753
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
16754
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
16755
|
+
context.fillStyle = shadowGradient;
|
|
16640
16756
|
context.beginPath();
|
|
16641
|
-
context.
|
|
16757
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
16642
16758
|
context.fill();
|
|
16643
16759
|
context.restore();
|
|
16644
16760
|
}
|
|
16761
|
+
/**
|
|
16762
|
+
* Number of latitude segments used by the single blobby octopus mesh.
|
|
16763
|
+
*
|
|
16764
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16765
|
+
*/
|
|
16766
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
16767
|
+
/**
|
|
16768
|
+
* Number of longitude segments used by the single blobby octopus mesh.
|
|
16769
|
+
*
|
|
16770
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16771
|
+
*/
|
|
16772
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
16645
16773
|
/**
|
|
16646
16774
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
16647
16775
|
*
|
|
16776
|
+
* Within a single frame, mesh corner samples and longitude-only lobe-wave values are
|
|
16777
|
+
* quantized to the patch grid and computed once each rather than re-evaluated for every
|
|
16778
|
+
* patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
|
|
16779
|
+
* `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
|
|
16780
|
+
* corners are shared between neighboring patches.
|
|
16781
|
+
*
|
|
16648
16782
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16649
16783
|
*/
|
|
16650
16784
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
16651
16785
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
16652
|
-
const latitudePatchCount =
|
|
16653
|
-
const longitudePatchCount =
|
|
16786
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
16787
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
16654
16788
|
const surfacePatches = [];
|
|
16789
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
16790
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
16791
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
16792
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
16793
|
+
}
|
|
16794
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16795
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
16796
|
+
}
|
|
16797
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
16798
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
16799
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
|
|
16800
|
+
}
|
|
16801
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
16802
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
16803
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
16804
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
16805
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
16806
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
16807
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
16808
|
+
const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
16809
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
16810
|
+
}
|
|
16811
|
+
}
|
|
16655
16812
|
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
16656
|
-
const startLatitude =
|
|
16657
|
-
const endLatitude =
|
|
16813
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
16814
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
16658
16815
|
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
16659
16816
|
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
16817
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
16818
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
16660
16819
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
16661
|
-
const startLongitude =
|
|
16662
|
-
const endLongitude =
|
|
16820
|
+
const startLongitude = longitudeBoundaries[longitudeIndex];
|
|
16821
|
+
const endLongitude = longitudeBoundaries[longitudeIndex + 1];
|
|
16663
16822
|
const centerLongitude = (startLongitude + endLongitude) / 2;
|
|
16664
|
-
const
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16823
|
+
const transformedCorners = [
|
|
16824
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
16825
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
16826
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
16827
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
16669
16828
|
];
|
|
16670
|
-
const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
|
|
16671
16829
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
16672
16830
|
if (surfaceNormal.z <= 0.01) {
|
|
16673
16831
|
continue;
|
|
16674
16832
|
}
|
|
16675
|
-
const projectedCorners =
|
|
16833
|
+
const projectedCorners = [
|
|
16834
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
16835
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
16836
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
16837
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
16838
|
+
];
|
|
16676
16839
|
surfacePatches.push({
|
|
16677
16840
|
corners: projectedCorners,
|
|
16678
|
-
averageDepth: transformedCorners.
|
|
16679
|
-
transformedCorners.
|
|
16680
|
-
|
|
16681
|
-
|
|
16841
|
+
averageDepth: (transformedCorners[0].z +
|
|
16842
|
+
transformedCorners[1].z +
|
|
16843
|
+
transformedCorners[2].z +
|
|
16844
|
+
transformedCorners[3].z) /
|
|
16845
|
+
4,
|
|
16846
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
16847
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
16682
16848
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
16683
16849
|
});
|
|
16684
16850
|
}
|
|
@@ -16694,12 +16860,21 @@
|
|
|
16694
16860
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16695
16861
|
*/
|
|
16696
16862
|
function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
|
|
16863
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
16864
|
+
return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
|
|
16865
|
+
}
|
|
16866
|
+
/**
|
|
16867
|
+
* Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
|
|
16868
|
+
* to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
|
|
16869
|
+
*
|
|
16870
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
16871
|
+
*/
|
|
16872
|
+
function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
|
|
16697
16873
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
16698
16874
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
16699
16875
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
16700
16876
|
const upperBlend = Math.pow(1 - verticalProgress, 1.2);
|
|
16701
16877
|
const lowerBlend = Math.pow(verticalProgress, 1.42);
|
|
16702
|
-
const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
|
|
16703
16878
|
const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
|
|
16704
16879
|
const horizontalScale = 1.02 +
|
|
16705
16880
|
skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
|
|
@@ -16737,7 +16912,7 @@
|
|
|
16737
16912
|
*
|
|
16738
16913
|
* @private helper of `octopus3d2AvatarVisual`
|
|
16739
16914
|
*/
|
|
16740
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16915
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
16741
16916
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
16742
16917
|
if (tonalProgress < 0.16) {
|
|
16743
16918
|
return palette.highlight;
|
|
@@ -16777,6 +16952,1384 @@
|
|
|
16777
16952
|
context.restore();
|
|
16778
16953
|
}
|
|
16779
16954
|
|
|
16955
|
+
/* eslint-disable no-magic-numbers */
|
|
16956
|
+
/**
|
|
16957
|
+
* Light direction used by the continuous octopus mesh shading.
|
|
16958
|
+
*
|
|
16959
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16960
|
+
*/
|
|
16961
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
16962
|
+
x: 0.34,
|
|
16963
|
+
y: -0.62,
|
|
16964
|
+
z: 1,
|
|
16965
|
+
});
|
|
16966
|
+
/**
|
|
16967
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
16968
|
+
*
|
|
16969
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16970
|
+
*/
|
|
16971
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
16972
|
+
/**
|
|
16973
|
+
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
16974
|
+
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
16975
|
+
* React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
|
|
16976
|
+
*
|
|
16977
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16978
|
+
*/
|
|
16979
|
+
const stableStateCache = new WeakMap();
|
|
16980
|
+
/**
|
|
16981
|
+
* Returns the stable per-avatar state, computing it on first access and returning the cached
|
|
16982
|
+
* result on every subsequent call within the same `<Avatar/>` mount.
|
|
16983
|
+
*
|
|
16984
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
16985
|
+
*/
|
|
16986
|
+
function getOctopus3d3StableState(createRandom) {
|
|
16987
|
+
const cached = stableStateCache.get(createRandom);
|
|
16988
|
+
if (cached !== undefined) {
|
|
16989
|
+
return cached;
|
|
16990
|
+
}
|
|
16991
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
16992
|
+
const animationRandom = createRandom('octopus3d3-animation-profile');
|
|
16993
|
+
const eyeRandom = createRandom('octopus3d3-eye-profile');
|
|
16994
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
16995
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
16996
|
+
const state = {
|
|
16997
|
+
morphologyProfile,
|
|
16998
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
16999
|
+
leftEyePhaseOffset,
|
|
17000
|
+
rightEyePhaseOffset,
|
|
17001
|
+
tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
17002
|
+
};
|
|
17003
|
+
stableStateCache.set(createRandom, state);
|
|
17004
|
+
return state;
|
|
17005
|
+
}
|
|
17006
|
+
/**
|
|
17007
|
+
* Octopus 3D 3 avatar visual.
|
|
17008
|
+
*
|
|
17009
|
+
* @private built-in avatar visual
|
|
17010
|
+
*/
|
|
17011
|
+
const octopus3d3AvatarVisual = {
|
|
17012
|
+
id: 'octopus3d3',
|
|
17013
|
+
title: 'Octopus 3D 3',
|
|
17014
|
+
description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
|
|
17015
|
+
isAnimated: true,
|
|
17016
|
+
supportsPointerTracking: true,
|
|
17017
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
17018
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
|
|
17019
|
+
const sceneCenterX = size * 0.5;
|
|
17020
|
+
const sceneCenterY = size * 0.535;
|
|
17021
|
+
const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
|
|
17022
|
+
const meshCenter = {
|
|
17023
|
+
x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
17024
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
17025
|
+
z: interaction.intensity * size * 0.018,
|
|
17026
|
+
};
|
|
17027
|
+
const rotationY = -0.1 +
|
|
17028
|
+
Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
|
|
17029
|
+
interaction.bodyOffsetX * 0.22 +
|
|
17030
|
+
interaction.gazeX * 0.88;
|
|
17031
|
+
const rotationX = -0.07 +
|
|
17032
|
+
Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
|
|
17033
|
+
interaction.bodyOffsetY * 0.08 -
|
|
17034
|
+
interaction.gazeY * 0.38;
|
|
17035
|
+
const surfaceOptions = {
|
|
17036
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
|
|
17037
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
|
|
17038
|
+
radiusZ: size *
|
|
17039
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
17040
|
+
(1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
|
|
17041
|
+
morphologyProfile,
|
|
17042
|
+
timeMs,
|
|
17043
|
+
animationPhase,
|
|
17044
|
+
tentacleProfiles,
|
|
17045
|
+
};
|
|
17046
|
+
const surfacePatches = resolveVisibleContinuousOctopusPatches({
|
|
17047
|
+
...surfaceOptions,
|
|
17048
|
+
center: meshCenter,
|
|
17049
|
+
rotationX,
|
|
17050
|
+
rotationY,
|
|
17051
|
+
sceneCenterX,
|
|
17052
|
+
sceneCenterY,
|
|
17053
|
+
size,
|
|
17054
|
+
palette,
|
|
17055
|
+
});
|
|
17056
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
|
|
17057
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
|
|
17058
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
17059
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
17060
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
17061
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
17062
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
|
|
17063
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
|
|
17064
|
+
drawAvatarFrame(context, size, palette);
|
|
17065
|
+
drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
17066
|
+
drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
17067
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
17068
|
+
drawContinuousSurfacePatch(context, surfacePatch);
|
|
17069
|
+
}
|
|
17070
|
+
drawProjectedSurfaceCurrents({
|
|
17071
|
+
context,
|
|
17072
|
+
surfaceOptions,
|
|
17073
|
+
center: meshCenter,
|
|
17074
|
+
rotationX,
|
|
17075
|
+
rotationY,
|
|
17076
|
+
sceneCenterX,
|
|
17077
|
+
sceneCenterY,
|
|
17078
|
+
size,
|
|
17079
|
+
palette,
|
|
17080
|
+
morphologyProfile,
|
|
17081
|
+
timeMs,
|
|
17082
|
+
animationPhase,
|
|
17083
|
+
});
|
|
17084
|
+
drawProjectedTentacleSuckers({
|
|
17085
|
+
context,
|
|
17086
|
+
surfaceOptions,
|
|
17087
|
+
center: meshCenter,
|
|
17088
|
+
rotationX,
|
|
17089
|
+
rotationY,
|
|
17090
|
+
sceneCenterX,
|
|
17091
|
+
sceneCenterY,
|
|
17092
|
+
size,
|
|
17093
|
+
palette,
|
|
17094
|
+
});
|
|
17095
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17096
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17097
|
+
drawProjectedOrganicMouth(context, [
|
|
17098
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
17099
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
17100
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
17101
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
17102
|
+
},
|
|
17103
|
+
};
|
|
17104
|
+
/**
|
|
17105
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
17106
|
+
*
|
|
17107
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17108
|
+
*/
|
|
17109
|
+
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17110
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
17111
|
+
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
17112
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
17113
|
+
return {
|
|
17114
|
+
centerLongitude: -Math.PI * 0.86 +
|
|
17115
|
+
progress * Math.PI * 1.72 +
|
|
17116
|
+
(tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
|
|
17117
|
+
widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
|
|
17118
|
+
lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
|
|
17119
|
+
swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
|
|
17120
|
+
depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
|
|
17121
|
+
phase: tentacleRandom() * Math.PI * 2,
|
|
17122
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
17123
|
+
};
|
|
17124
|
+
});
|
|
17125
|
+
}
|
|
17126
|
+
/**
|
|
17127
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
17128
|
+
*
|
|
17129
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17130
|
+
*/
|
|
17131
|
+
function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
17132
|
+
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));
|
|
17133
|
+
glowGradient.addColorStop(0, `${palette.highlight}66`);
|
|
17134
|
+
glowGradient.addColorStop(0.34, `${palette.accent}2e`);
|
|
17135
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
17136
|
+
context.fillStyle = glowGradient;
|
|
17137
|
+
context.fillRect(0, 0, size, size);
|
|
17138
|
+
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);
|
|
17139
|
+
lowerGradient.addColorStop(0, `${palette.secondary}25`);
|
|
17140
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
17141
|
+
context.fillStyle = lowerGradient;
|
|
17142
|
+
context.fillRect(0, 0, size, size);
|
|
17143
|
+
}
|
|
17144
|
+
/**
|
|
17145
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
17146
|
+
*
|
|
17147
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
17148
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
17149
|
+
*
|
|
17150
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17151
|
+
*/
|
|
17152
|
+
function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
17153
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
17154
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
17155
|
+
const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
|
|
17156
|
+
const ry = size * 0.06;
|
|
17157
|
+
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
17158
|
+
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
17159
|
+
context.save();
|
|
17160
|
+
context.translate(cx, cy);
|
|
17161
|
+
context.scale(1, ry / rx);
|
|
17162
|
+
const blurRadius = rx * 1.4;
|
|
17163
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
17164
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
17165
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
17166
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
17167
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
17168
|
+
context.fillStyle = shadowGradient;
|
|
17169
|
+
context.beginPath();
|
|
17170
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
17171
|
+
context.fill();
|
|
17172
|
+
context.restore();
|
|
17173
|
+
}
|
|
17174
|
+
/**
|
|
17175
|
+
* Number of latitude segments used by the continuous Octopus 3D 3 mesh.
|
|
17176
|
+
*
|
|
17177
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17178
|
+
*/
|
|
17179
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
17180
|
+
/**
|
|
17181
|
+
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
17182
|
+
*
|
|
17183
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17184
|
+
*/
|
|
17185
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
17186
|
+
/**
|
|
17187
|
+
* Resolves visible projected patches for the continuous octopus mesh.
|
|
17188
|
+
*
|
|
17189
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
17190
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
17191
|
+
* than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
|
|
17192
|
+
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
17193
|
+
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
17194
|
+
*
|
|
17195
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17196
|
+
*/
|
|
17197
|
+
function resolveVisibleContinuousOctopusPatches(options) {
|
|
17198
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17199
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
17200
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
17201
|
+
const surfacePatches = [];
|
|
17202
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17203
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
17204
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
17205
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
17206
|
+
}
|
|
17207
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17208
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
17209
|
+
}
|
|
17210
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
17211
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
17212
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
17213
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17214
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17215
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17216
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
17217
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
|
|
17218
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
|
|
17219
|
+
}
|
|
17220
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17221
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
17222
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
|
|
17223
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
|
|
17224
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
17225
|
+
}
|
|
17226
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
17227
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
17228
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
17229
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
17230
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
17231
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
17232
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
17233
|
+
const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
17234
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
17235
|
+
}
|
|
17236
|
+
}
|
|
17237
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
17238
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
17239
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
17240
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
17241
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
17242
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
17243
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
17244
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17245
|
+
const transformedCorners = [
|
|
17246
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
17247
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
17248
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
17249
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
17250
|
+
];
|
|
17251
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
17252
|
+
if (surfaceNormal.z <= 0.008) {
|
|
17253
|
+
continue;
|
|
17254
|
+
}
|
|
17255
|
+
const projectedCorners = [
|
|
17256
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
17257
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
17258
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
17259
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
17260
|
+
];
|
|
17261
|
+
surfacePatches.push({
|
|
17262
|
+
corners: projectedCorners,
|
|
17263
|
+
averageDepth: (transformedCorners[0].z +
|
|
17264
|
+
transformedCorners[1].z +
|
|
17265
|
+
transformedCorners[2].z +
|
|
17266
|
+
transformedCorners[3].z) /
|
|
17267
|
+
4,
|
|
17268
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
17269
|
+
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17270
|
+
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
17271
|
+
});
|
|
17272
|
+
}
|
|
17273
|
+
}
|
|
17274
|
+
return surfacePatches;
|
|
17275
|
+
}
|
|
17276
|
+
/**
|
|
17277
|
+
* Samples one point on the continuous Octopus 3D 3 surface.
|
|
17278
|
+
*
|
|
17279
|
+
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
17280
|
+
* tentacled while still being rendered as one connected blobby mesh.
|
|
17281
|
+
*
|
|
17282
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17283
|
+
*/
|
|
17284
|
+
function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
|
|
17285
|
+
return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
|
|
17286
|
+
}
|
|
17287
|
+
/**
|
|
17288
|
+
* Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
|
|
17289
|
+
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
17290
|
+
*
|
|
17291
|
+
* The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
|
|
17292
|
+
* corner grid, so the same longitude is reused across every latitude row and each
|
|
17293
|
+
* tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
|
|
17294
|
+
*
|
|
17295
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17296
|
+
*/
|
|
17297
|
+
function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
17298
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
17299
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17300
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17301
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
17302
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
17303
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
17304
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
17305
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
17306
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17307
|
+
animationPhase * 0.6 +
|
|
17308
|
+
timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
|
|
17309
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
|
|
17310
|
+
(0.3 + lowerBlend * 0.7);
|
|
17311
|
+
const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
|
|
17312
|
+
lowerBlend *
|
|
17313
|
+
tentacleInfluence.core *
|
|
17314
|
+
tentacleInfluence.swayScale;
|
|
17315
|
+
const horizontalScale = 1.04 +
|
|
17316
|
+
mantleRipple +
|
|
17317
|
+
lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
|
|
17318
|
+
lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
|
|
17319
|
+
upperBlend * 0.08;
|
|
17320
|
+
const depthScale = 1.06 +
|
|
17321
|
+
upperBlend * 0.16 +
|
|
17322
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
|
|
17323
|
+
lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
|
|
17324
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
17325
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
|
|
17326
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
17327
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
|
|
17328
|
+
const lowerDrop = lowerBlend *
|
|
17329
|
+
radiusY *
|
|
17330
|
+
(0.18 +
|
|
17331
|
+
tentacleInfluence.core *
|
|
17332
|
+
(0.38 +
|
|
17333
|
+
tentacleInfluence.lengthScale * 0.22 +
|
|
17334
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
|
|
17335
|
+
return {
|
|
17336
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
|
|
17337
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
|
|
17338
|
+
upperBlend * radiusY * 0.1 +
|
|
17339
|
+
lowerDrop +
|
|
17340
|
+
Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
|
|
17341
|
+
Math.cos(timeMs / 880 + tentacleInfluence.phase) *
|
|
17342
|
+
lowerBlend *
|
|
17343
|
+
tipBlend *
|
|
17344
|
+
tentacleInfluence.core *
|
|
17345
|
+
radiusY *
|
|
17346
|
+
0.034,
|
|
17347
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
17348
|
+
Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
|
|
17349
|
+
lowerBlend *
|
|
17350
|
+
tentacleInfluence.core *
|
|
17351
|
+
radiusZ *
|
|
17352
|
+
0.04,
|
|
17353
|
+
};
|
|
17354
|
+
}
|
|
17355
|
+
/**
|
|
17356
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
17357
|
+
*
|
|
17358
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17359
|
+
*/
|
|
17360
|
+
function resolveContinuousTentacleInfluence(options, longitude) {
|
|
17361
|
+
let totalWeight = 0;
|
|
17362
|
+
let weightedSin = 0;
|
|
17363
|
+
let weightedCos = 0;
|
|
17364
|
+
let weightedWidthScale = 0;
|
|
17365
|
+
let weightedLengthScale = 0;
|
|
17366
|
+
let weightedSwayScale = 0;
|
|
17367
|
+
let weightedDepthScale = 0;
|
|
17368
|
+
let weightedPhase = 0;
|
|
17369
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
17370
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
17371
|
+
const width = 0.2 * tentacleProfile.widthScale;
|
|
17372
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
17373
|
+
totalWeight += weight;
|
|
17374
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
17375
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
17376
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
17377
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
17378
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
17379
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
17380
|
+
weightedPhase += tentacleProfile.phase * weight;
|
|
17381
|
+
}
|
|
17382
|
+
if (totalWeight < 0.0001) {
|
|
17383
|
+
return {
|
|
17384
|
+
core: 0,
|
|
17385
|
+
centerLongitude: longitude,
|
|
17386
|
+
widthScale: 1,
|
|
17387
|
+
lengthScale: 1,
|
|
17388
|
+
swayScale: 1,
|
|
17389
|
+
depthScale: 1,
|
|
17390
|
+
phase: 0,
|
|
17391
|
+
};
|
|
17392
|
+
}
|
|
17393
|
+
return {
|
|
17394
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
17395
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
17396
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
17397
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
17398
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
17399
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
17400
|
+
phase: weightedPhase / totalWeight,
|
|
17401
|
+
};
|
|
17402
|
+
}
|
|
17403
|
+
/**
|
|
17404
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
17405
|
+
*
|
|
17406
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17407
|
+
*/
|
|
17408
|
+
function resolveContinuousLobeWave(options, longitude) {
|
|
17409
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
17410
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
17411
|
+
animationPhase +
|
|
17412
|
+
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
17413
|
+
1) /
|
|
17414
|
+
2);
|
|
17415
|
+
}
|
|
17416
|
+
/**
|
|
17417
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
17418
|
+
*
|
|
17419
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17420
|
+
*/
|
|
17421
|
+
function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
17422
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
17423
|
+
if (tonalProgress < 0.14) {
|
|
17424
|
+
return palette.highlight;
|
|
17425
|
+
}
|
|
17426
|
+
if (tonalProgress < 0.32) {
|
|
17427
|
+
return palette.secondary;
|
|
17428
|
+
}
|
|
17429
|
+
if (tonalProgress < 0.72) {
|
|
17430
|
+
return forwardness > 0.55 ? palette.secondary : palette.primary;
|
|
17431
|
+
}
|
|
17432
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
17433
|
+
}
|
|
17434
|
+
/**
|
|
17435
|
+
* Draws one projected mesh patch with soft shading and a subtle edge.
|
|
17436
|
+
*
|
|
17437
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17438
|
+
*/
|
|
17439
|
+
function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
17440
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
17441
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
17442
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
|
|
17443
|
+
}
|
|
17444
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
17445
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
17446
|
+
}
|
|
17447
|
+
context.save();
|
|
17448
|
+
context.beginPath();
|
|
17449
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
17450
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
17451
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
17452
|
+
}
|
|
17453
|
+
context.closePath();
|
|
17454
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
17455
|
+
context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
|
|
17456
|
+
context.lineJoin = 'round';
|
|
17457
|
+
context.stroke();
|
|
17458
|
+
context.restore();
|
|
17459
|
+
}
|
|
17460
|
+
/**
|
|
17461
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
17462
|
+
*
|
|
17463
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17464
|
+
*/
|
|
17465
|
+
function drawProjectedSurfaceCurrents(options) {
|
|
17466
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
17467
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
17468
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
17469
|
+
context.save();
|
|
17470
|
+
context.lineCap = 'round';
|
|
17471
|
+
context.lineJoin = 'round';
|
|
17472
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
17473
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
17474
|
+
const projectedPoints = [];
|
|
17475
|
+
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
|
|
17476
|
+
const progress = sampleIndex / 7;
|
|
17477
|
+
const latitude = -0.46 + progress * 0.74;
|
|
17478
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
|
|
17479
|
+
const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
17480
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
17481
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
17482
|
+
}
|
|
17483
|
+
}
|
|
17484
|
+
if (projectedPoints.length < 3) {
|
|
17485
|
+
continue;
|
|
17486
|
+
}
|
|
17487
|
+
context.beginPath();
|
|
17488
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
17489
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
17490
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
17491
|
+
}
|
|
17492
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
17493
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
17494
|
+
context.stroke();
|
|
17495
|
+
}
|
|
17496
|
+
context.restore();
|
|
17497
|
+
}
|
|
17498
|
+
/**
|
|
17499
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
17500
|
+
*
|
|
17501
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17502
|
+
*/
|
|
17503
|
+
function drawProjectedTentacleSuckers(options) {
|
|
17504
|
+
const { surfaceOptions, size } = options;
|
|
17505
|
+
const { timeMs } = surfaceOptions;
|
|
17506
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
17507
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
|
|
17508
|
+
continue;
|
|
17509
|
+
}
|
|
17510
|
+
for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
|
|
17511
|
+
const latitude = 0.52 + suckerIndex * 0.14;
|
|
17512
|
+
const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
|
|
17513
|
+
const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
|
|
17514
|
+
drawProjectedSurfaceSpot({
|
|
17515
|
+
...options,
|
|
17516
|
+
latitude,
|
|
17517
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
17518
|
+
radiusScale: size * (0.0065 - suckerIndex * 0.0007),
|
|
17519
|
+
});
|
|
17520
|
+
}
|
|
17521
|
+
}
|
|
17522
|
+
}
|
|
17523
|
+
/**
|
|
17524
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
17525
|
+
*
|
|
17526
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17527
|
+
*/
|
|
17528
|
+
function drawProjectedSurfaceSpot(options) {
|
|
17529
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
17530
|
+
const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
|
|
17531
|
+
const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
17532
|
+
const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
17533
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
17534
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
17535
|
+
return;
|
|
17536
|
+
}
|
|
17537
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
17538
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17539
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
17540
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
17541
|
+
radiusScale *
|
|
17542
|
+
0.74, size * 0.003, size * 0.018);
|
|
17543
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
17544
|
+
radiusScale *
|
|
17545
|
+
0.52, size * 0.0024, size * 0.014);
|
|
17546
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
17547
|
+
context.save();
|
|
17548
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
17549
|
+
context.rotate(rotation);
|
|
17550
|
+
context.beginPath();
|
|
17551
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
17552
|
+
context.fillStyle = `${palette.highlight}73`;
|
|
17553
|
+
context.fill();
|
|
17554
|
+
context.strokeStyle = `${palette.highlight}99`;
|
|
17555
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
17556
|
+
context.stroke();
|
|
17557
|
+
context.restore();
|
|
17558
|
+
}
|
|
17559
|
+
/**
|
|
17560
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
17561
|
+
*
|
|
17562
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17563
|
+
*/
|
|
17564
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
17565
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
17566
|
+
}
|
|
17567
|
+
/**
|
|
17568
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
17569
|
+
*
|
|
17570
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
17571
|
+
*/
|
|
17572
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
17573
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
17574
|
+
return progress * progress * (3 - 2 * progress);
|
|
17575
|
+
}
|
|
17576
|
+
|
|
17577
|
+
/* eslint-disable no-magic-numbers */
|
|
17578
|
+
/**
|
|
17579
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
17580
|
+
*
|
|
17581
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17582
|
+
*/
|
|
17583
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
17584
|
+
x: 0.32,
|
|
17585
|
+
y: -0.66,
|
|
17586
|
+
z: 1,
|
|
17587
|
+
});
|
|
17588
|
+
/**
|
|
17589
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
17590
|
+
*
|
|
17591
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17592
|
+
*/
|
|
17593
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
17594
|
+
x: -0.7,
|
|
17595
|
+
y: -0.18,
|
|
17596
|
+
z: 0.55,
|
|
17597
|
+
});
|
|
17598
|
+
/**
|
|
17599
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
17600
|
+
*
|
|
17601
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17602
|
+
*/
|
|
17603
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
17604
|
+
/**
|
|
17605
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
17606
|
+
*
|
|
17607
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17608
|
+
*/
|
|
17609
|
+
const SKIN_SPOT_COUNT = 14;
|
|
17610
|
+
/**
|
|
17611
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
17612
|
+
*
|
|
17613
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17614
|
+
*/
|
|
17615
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
17616
|
+
/**
|
|
17617
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
17618
|
+
*
|
|
17619
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17620
|
+
*/
|
|
17621
|
+
function getOctopus3d4StableState(createRandom) {
|
|
17622
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
17623
|
+
if (cached !== undefined) {
|
|
17624
|
+
return cached;
|
|
17625
|
+
}
|
|
17626
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
17627
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
17628
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
17629
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
17630
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
17631
|
+
const state = {
|
|
17632
|
+
morphologyProfile,
|
|
17633
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
17634
|
+
leftEyePhaseOffset,
|
|
17635
|
+
rightEyePhaseOffset,
|
|
17636
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
17637
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
17638
|
+
};
|
|
17639
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
17640
|
+
return state;
|
|
17641
|
+
}
|
|
17642
|
+
/**
|
|
17643
|
+
* Octopus 3D 4 avatar visual.
|
|
17644
|
+
*
|
|
17645
|
+
* @private built-in avatar visual
|
|
17646
|
+
*/
|
|
17647
|
+
const octopus3d4AvatarVisual = {
|
|
17648
|
+
id: 'octopus3d4',
|
|
17649
|
+
title: 'Octopus 3D 4',
|
|
17650
|
+
description: 'Cute blobby 3D octopus rendered as one smooth continuous mesh with naturally undulating tentacles, rich multi-pass shading, pigment spots, and cursor-aware head and eyes.',
|
|
17651
|
+
isAnimated: true,
|
|
17652
|
+
supportsPointerTracking: true,
|
|
17653
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
17654
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
17655
|
+
const sceneCenterX = size * 0.5;
|
|
17656
|
+
const sceneCenterY = size * 0.535;
|
|
17657
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
17658
|
+
const meshCenter = {
|
|
17659
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
17660
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
17661
|
+
z: interaction.intensity * size * 0.02,
|
|
17662
|
+
};
|
|
17663
|
+
const rotationY = -0.08 +
|
|
17664
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
17665
|
+
interaction.bodyOffsetX * 0.24 +
|
|
17666
|
+
interaction.gazeX * 0.98;
|
|
17667
|
+
const rotationX = -0.07 +
|
|
17668
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
17669
|
+
interaction.bodyOffsetY * 0.08 -
|
|
17670
|
+
interaction.gazeY * 0.42;
|
|
17671
|
+
const surfaceOptions = {
|
|
17672
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
17673
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
17674
|
+
radiusZ: size *
|
|
17675
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
17676
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
17677
|
+
morphologyProfile,
|
|
17678
|
+
timeMs,
|
|
17679
|
+
animationPhase,
|
|
17680
|
+
tentacleProfiles,
|
|
17681
|
+
};
|
|
17682
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
17683
|
+
...surfaceOptions,
|
|
17684
|
+
center: meshCenter,
|
|
17685
|
+
rotationX,
|
|
17686
|
+
rotationY,
|
|
17687
|
+
sceneCenterX,
|
|
17688
|
+
sceneCenterY,
|
|
17689
|
+
size,
|
|
17690
|
+
palette,
|
|
17691
|
+
});
|
|
17692
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
17693
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
17694
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
17695
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
17696
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
17697
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
17698
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
17699
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
17700
|
+
drawAvatarFrame(context, size, palette);
|
|
17701
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
17702
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
17703
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
17704
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
17705
|
+
}
|
|
17706
|
+
drawBlobbySkinSpots({
|
|
17707
|
+
context,
|
|
17708
|
+
surfaceOptions,
|
|
17709
|
+
center: meshCenter,
|
|
17710
|
+
rotationX,
|
|
17711
|
+
rotationY,
|
|
17712
|
+
sceneCenterX,
|
|
17713
|
+
sceneCenterY,
|
|
17714
|
+
size,
|
|
17715
|
+
palette,
|
|
17716
|
+
skinSpots,
|
|
17717
|
+
});
|
|
17718
|
+
drawBlobbyContinuousCurrents({
|
|
17719
|
+
context,
|
|
17720
|
+
surfaceOptions,
|
|
17721
|
+
center: meshCenter,
|
|
17722
|
+
rotationX,
|
|
17723
|
+
rotationY,
|
|
17724
|
+
sceneCenterX,
|
|
17725
|
+
sceneCenterY,
|
|
17726
|
+
size,
|
|
17727
|
+
palette,
|
|
17728
|
+
morphologyProfile,
|
|
17729
|
+
timeMs,
|
|
17730
|
+
animationPhase,
|
|
17731
|
+
});
|
|
17732
|
+
drawBlobbyContinuousSuckers({
|
|
17733
|
+
context,
|
|
17734
|
+
surfaceOptions,
|
|
17735
|
+
center: meshCenter,
|
|
17736
|
+
rotationX,
|
|
17737
|
+
rotationY,
|
|
17738
|
+
sceneCenterX,
|
|
17739
|
+
sceneCenterY,
|
|
17740
|
+
size,
|
|
17741
|
+
palette,
|
|
17742
|
+
});
|
|
17743
|
+
drawBlobbyContinuousGloss({
|
|
17744
|
+
context,
|
|
17745
|
+
surfaceOptions,
|
|
17746
|
+
center: meshCenter,
|
|
17747
|
+
rotationX,
|
|
17748
|
+
rotationY,
|
|
17749
|
+
sceneCenterX,
|
|
17750
|
+
sceneCenterY,
|
|
17751
|
+
size,
|
|
17752
|
+
palette,
|
|
17753
|
+
});
|
|
17754
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17755
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
17756
|
+
drawProjectedOrganicMouth(context, [
|
|
17757
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
17758
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
17759
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
17760
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
17761
|
+
},
|
|
17762
|
+
};
|
|
17763
|
+
/**
|
|
17764
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
17765
|
+
*
|
|
17766
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17767
|
+
*/
|
|
17768
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
17769
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
17770
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
17771
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
17772
|
+
return {
|
|
17773
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
17774
|
+
progress * Math.PI * 1.8 +
|
|
17775
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
17776
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
17777
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
17778
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
17779
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
17780
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
17781
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17782
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
17783
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
17784
|
+
};
|
|
17785
|
+
});
|
|
17786
|
+
}
|
|
17787
|
+
/**
|
|
17788
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
17789
|
+
*
|
|
17790
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17791
|
+
*/
|
|
17792
|
+
function createBlobbySkinSpots(createRandom) {
|
|
17793
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
17794
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
17795
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
17796
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
17797
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
17798
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
17799
|
+
}));
|
|
17800
|
+
}
|
|
17801
|
+
/**
|
|
17802
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
17803
|
+
*
|
|
17804
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17805
|
+
*/
|
|
17806
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
17807
|
+
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.12, sceneCenterY - size * 0.18 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY, size * (0.68 + interaction.intensity * 0.025));
|
|
17808
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
17809
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
17810
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
17811
|
+
context.fillStyle = glowGradient;
|
|
17812
|
+
context.fillRect(0, 0, size, size);
|
|
17813
|
+
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1500) * size * 0.05, sceneCenterY + size * 0.3, size * 0.06, sceneCenterX, sceneCenterY + size * 0.3, size * 0.56);
|
|
17814
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
17815
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
17816
|
+
context.fillStyle = lowerGradient;
|
|
17817
|
+
context.fillRect(0, 0, size, size);
|
|
17818
|
+
}
|
|
17819
|
+
/**
|
|
17820
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
17821
|
+
*
|
|
17822
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17823
|
+
*/
|
|
17824
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
17825
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
17826
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
17827
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
17828
|
+
const ry = size * 0.062;
|
|
17829
|
+
context.save();
|
|
17830
|
+
context.translate(cx, cy);
|
|
17831
|
+
context.scale(1, ry / rx);
|
|
17832
|
+
const blurRadius = rx * 1.42;
|
|
17833
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
17834
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
17835
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
17836
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
17837
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
17838
|
+
context.fillStyle = shadowGradient;
|
|
17839
|
+
context.beginPath();
|
|
17840
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
17841
|
+
context.fill();
|
|
17842
|
+
context.restore();
|
|
17843
|
+
}
|
|
17844
|
+
/**
|
|
17845
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17846
|
+
*
|
|
17847
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17848
|
+
*/
|
|
17849
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
17850
|
+
/**
|
|
17851
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
17852
|
+
*
|
|
17853
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17854
|
+
*/
|
|
17855
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
17856
|
+
/**
|
|
17857
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
17858
|
+
*
|
|
17859
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
17860
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
17861
|
+
* than re-evaluated for every patch corner.
|
|
17862
|
+
*
|
|
17863
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17864
|
+
*/
|
|
17865
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
17866
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
17867
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
17868
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
17869
|
+
const surfacePatches = [];
|
|
17870
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
17871
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
17872
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
17873
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
17874
|
+
}
|
|
17875
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17876
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
17877
|
+
}
|
|
17878
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
17879
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
17880
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
17881
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17882
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
17883
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
17884
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
17885
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
17886
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
17887
|
+
}
|
|
17888
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17889
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
17890
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
17891
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
17892
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
17893
|
+
}
|
|
17894
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
17895
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
17896
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
17897
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
17898
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
17899
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
17900
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
17901
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
17902
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
17903
|
+
}
|
|
17904
|
+
}
|
|
17905
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
17906
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
17907
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
17908
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
17909
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
17910
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
17911
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
17912
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
17913
|
+
const transformedCorners = [
|
|
17914
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
17915
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
17916
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
17917
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
17918
|
+
];
|
|
17919
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
17920
|
+
if (surfaceNormal.z <= 0.006) {
|
|
17921
|
+
continue;
|
|
17922
|
+
}
|
|
17923
|
+
const projectedCorners = [
|
|
17924
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
17925
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
17926
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
17927
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
17928
|
+
];
|
|
17929
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
17930
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
17931
|
+
surfacePatches.push({
|
|
17932
|
+
corners: projectedCorners,
|
|
17933
|
+
averageDepth: (transformedCorners[0].z +
|
|
17934
|
+
transformedCorners[1].z +
|
|
17935
|
+
transformedCorners[2].z +
|
|
17936
|
+
transformedCorners[3].z) /
|
|
17937
|
+
4,
|
|
17938
|
+
lightIntensity,
|
|
17939
|
+
rimLightIntensity,
|
|
17940
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
17941
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
17942
|
+
});
|
|
17943
|
+
}
|
|
17944
|
+
}
|
|
17945
|
+
return surfacePatches;
|
|
17946
|
+
}
|
|
17947
|
+
/**
|
|
17948
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
17949
|
+
*
|
|
17950
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17951
|
+
*/
|
|
17952
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
17953
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
17954
|
+
}
|
|
17955
|
+
/**
|
|
17956
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
17957
|
+
*
|
|
17958
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
17959
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
17960
|
+
* of `latitudePatchCount * 4` times.
|
|
17961
|
+
*
|
|
17962
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
17963
|
+
*/
|
|
17964
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
17965
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
17966
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
17967
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
17968
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
17969
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
17970
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
17971
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
17972
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
17973
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
17974
|
+
animationPhase * 0.6 +
|
|
17975
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
17976
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
17977
|
+
(0.32 + lowerBlend * 0.7);
|
|
17978
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
17979
|
+
lowerBlend *
|
|
17980
|
+
tentacleInfluence.core *
|
|
17981
|
+
tentacleInfluence.swayScale;
|
|
17982
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
17983
|
+
lowerBlend *
|
|
17984
|
+
tentacleInfluence.core *
|
|
17985
|
+
tentacleInfluence.swayScale *
|
|
17986
|
+
0.42;
|
|
17987
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
17988
|
+
tipBlend *
|
|
17989
|
+
tentacleInfluence.core *
|
|
17990
|
+
tentacleInfluence.curlScale *
|
|
17991
|
+
0.36;
|
|
17992
|
+
const horizontalScale = 1.06 +
|
|
17993
|
+
mantleRipple +
|
|
17994
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
17995
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
17996
|
+
upperBlend * 0.08;
|
|
17997
|
+
const depthScale = 1.08 +
|
|
17998
|
+
upperBlend * 0.16 +
|
|
17999
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
18000
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
18001
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
18002
|
+
const tentacleTubeRadius = lowerBlend *
|
|
18003
|
+
tentacleInfluence.core *
|
|
18004
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
18005
|
+
radiusX;
|
|
18006
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
18007
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
18008
|
+
const lowerDrop = lowerBlend *
|
|
18009
|
+
radiusY *
|
|
18010
|
+
(0.2 +
|
|
18011
|
+
tentacleInfluence.core *
|
|
18012
|
+
(0.42 +
|
|
18013
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
18014
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
18015
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
18016
|
+
return {
|
|
18017
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
18018
|
+
combinedTentacleSway +
|
|
18019
|
+
tentacleCurl * radiusX * 0.18,
|
|
18020
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
18021
|
+
upperBlend * radiusY * 0.12 +
|
|
18022
|
+
lowerDrop +
|
|
18023
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
18024
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
18025
|
+
lowerBlend *
|
|
18026
|
+
tipBlend *
|
|
18027
|
+
tentacleInfluence.core *
|
|
18028
|
+
radiusY *
|
|
18029
|
+
0.04,
|
|
18030
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
18031
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
18032
|
+
lowerBlend *
|
|
18033
|
+
tentacleInfluence.core *
|
|
18034
|
+
radiusZ *
|
|
18035
|
+
0.044 +
|
|
18036
|
+
tentacleCurl * radiusZ * 0.14,
|
|
18037
|
+
};
|
|
18038
|
+
}
|
|
18039
|
+
/**
|
|
18040
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
18041
|
+
*
|
|
18042
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18043
|
+
*/
|
|
18044
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
18045
|
+
let totalWeight = 0;
|
|
18046
|
+
let weightedSin = 0;
|
|
18047
|
+
let weightedCos = 0;
|
|
18048
|
+
let weightedWidthScale = 0;
|
|
18049
|
+
let weightedLengthScale = 0;
|
|
18050
|
+
let weightedSwayScale = 0;
|
|
18051
|
+
let weightedDepthScale = 0;
|
|
18052
|
+
let weightedCurlScale = 0;
|
|
18053
|
+
let weightedPrimaryPhase = 0;
|
|
18054
|
+
let weightedSecondaryPhase = 0;
|
|
18055
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
18056
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
18057
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
18058
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
18059
|
+
totalWeight += weight;
|
|
18060
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
18061
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
18062
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
18063
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
18064
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
18065
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
18066
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
18067
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
18068
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
18069
|
+
}
|
|
18070
|
+
if (totalWeight < 0.0001) {
|
|
18071
|
+
return {
|
|
18072
|
+
core: 0,
|
|
18073
|
+
centerLongitude: longitude,
|
|
18074
|
+
widthScale: 1,
|
|
18075
|
+
lengthScale: 1,
|
|
18076
|
+
swayScale: 1,
|
|
18077
|
+
depthScale: 1,
|
|
18078
|
+
curlScale: 1,
|
|
18079
|
+
primaryPhase: 0,
|
|
18080
|
+
secondaryPhase: 0,
|
|
18081
|
+
};
|
|
18082
|
+
}
|
|
18083
|
+
return {
|
|
18084
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
18085
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
18086
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
18087
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
18088
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
18089
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
18090
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
18091
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
18092
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
18093
|
+
};
|
|
18094
|
+
}
|
|
18095
|
+
/**
|
|
18096
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
18097
|
+
*
|
|
18098
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18099
|
+
*/
|
|
18100
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
18101
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
18102
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
18103
|
+
animationPhase +
|
|
18104
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
18105
|
+
1) /
|
|
18106
|
+
2);
|
|
18107
|
+
}
|
|
18108
|
+
/**
|
|
18109
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
18110
|
+
*
|
|
18111
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18112
|
+
*/
|
|
18113
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
18114
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
18115
|
+
if (tonalProgress < 0.12) {
|
|
18116
|
+
return palette.highlight;
|
|
18117
|
+
}
|
|
18118
|
+
if (tonalProgress < 0.3) {
|
|
18119
|
+
return palette.secondary;
|
|
18120
|
+
}
|
|
18121
|
+
if (tonalProgress < 0.72) {
|
|
18122
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
18123
|
+
}
|
|
18124
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
18125
|
+
}
|
|
18126
|
+
/**
|
|
18127
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
18128
|
+
*
|
|
18129
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18130
|
+
*/
|
|
18131
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
18132
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
18133
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
18134
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
18135
|
+
}
|
|
18136
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
18137
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
18138
|
+
}
|
|
18139
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
18140
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
18141
|
+
}
|
|
18142
|
+
context.save();
|
|
18143
|
+
context.beginPath();
|
|
18144
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
18145
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
18146
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
18147
|
+
}
|
|
18148
|
+
context.closePath();
|
|
18149
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
18150
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
18151
|
+
context.lineJoin = 'round';
|
|
18152
|
+
context.stroke();
|
|
18153
|
+
context.restore();
|
|
18154
|
+
}
|
|
18155
|
+
/**
|
|
18156
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
18157
|
+
*
|
|
18158
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18159
|
+
*/
|
|
18160
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
18161
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
18162
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
18163
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
18164
|
+
context.save();
|
|
18165
|
+
context.lineCap = 'round';
|
|
18166
|
+
context.lineJoin = 'round';
|
|
18167
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
18168
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
18169
|
+
const projectedPoints = [];
|
|
18170
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
18171
|
+
const progress = sampleIndex / 8;
|
|
18172
|
+
const latitude = -0.48 + progress * 0.78;
|
|
18173
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
18174
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
18175
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
18176
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
18177
|
+
}
|
|
18178
|
+
}
|
|
18179
|
+
if (projectedPoints.length < 3) {
|
|
18180
|
+
continue;
|
|
18181
|
+
}
|
|
18182
|
+
context.beginPath();
|
|
18183
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
18184
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
18185
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
18186
|
+
}
|
|
18187
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
18188
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
18189
|
+
context.stroke();
|
|
18190
|
+
}
|
|
18191
|
+
context.restore();
|
|
18192
|
+
}
|
|
18193
|
+
/**
|
|
18194
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
18195
|
+
*
|
|
18196
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18197
|
+
*/
|
|
18198
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
18199
|
+
const { surfaceOptions, size } = options;
|
|
18200
|
+
const { timeMs } = surfaceOptions;
|
|
18201
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
18202
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
18203
|
+
continue;
|
|
18204
|
+
}
|
|
18205
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
18206
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
18207
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
18208
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
18209
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
18210
|
+
...options,
|
|
18211
|
+
latitude,
|
|
18212
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
18213
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
18214
|
+
});
|
|
18215
|
+
}
|
|
18216
|
+
}
|
|
18217
|
+
}
|
|
18218
|
+
/**
|
|
18219
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
18220
|
+
*
|
|
18221
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18222
|
+
*/
|
|
18223
|
+
function drawBlobbySkinSpots(options) {
|
|
18224
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
18225
|
+
for (const skinSpot of skinSpots) {
|
|
18226
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
18227
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18228
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
18229
|
+
continue;
|
|
18230
|
+
}
|
|
18231
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18232
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
18233
|
+
context.save();
|
|
18234
|
+
context.beginPath();
|
|
18235
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
18236
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
18237
|
+
context.fill();
|
|
18238
|
+
context.restore();
|
|
18239
|
+
}
|
|
18240
|
+
}
|
|
18241
|
+
/**
|
|
18242
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
18243
|
+
*
|
|
18244
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18245
|
+
*/
|
|
18246
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
18247
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
18248
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
18249
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
18250
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
18251
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18252
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
18253
|
+
return;
|
|
18254
|
+
}
|
|
18255
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18256
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18257
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
18258
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
18259
|
+
radiusScale *
|
|
18260
|
+
0.78, size * 0.003, size * 0.02);
|
|
18261
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
18262
|
+
radiusScale *
|
|
18263
|
+
0.54, size * 0.0024, size * 0.015);
|
|
18264
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
18265
|
+
context.save();
|
|
18266
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
18267
|
+
context.rotate(rotation);
|
|
18268
|
+
context.beginPath();
|
|
18269
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
18270
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
18271
|
+
context.fill();
|
|
18272
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
18273
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
18274
|
+
context.stroke();
|
|
18275
|
+
context.restore();
|
|
18276
|
+
}
|
|
18277
|
+
/**
|
|
18278
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
18279
|
+
*
|
|
18280
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18281
|
+
*/
|
|
18282
|
+
function drawBlobbyContinuousGloss(options) {
|
|
18283
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
18284
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
18285
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
18286
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
18287
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
18288
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
18289
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
18290
|
+
return;
|
|
18291
|
+
}
|
|
18292
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
18293
|
+
const glossRadius = size * 0.058;
|
|
18294
|
+
context.save();
|
|
18295
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
18296
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
18297
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
18298
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
18299
|
+
context.fillStyle = glossGradient;
|
|
18300
|
+
context.beginPath();
|
|
18301
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
18302
|
+
context.fill();
|
|
18303
|
+
context.restore();
|
|
18304
|
+
}
|
|
18305
|
+
/**
|
|
18306
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
18307
|
+
*
|
|
18308
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18309
|
+
*/
|
|
18310
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
18311
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
18312
|
+
}
|
|
18313
|
+
/**
|
|
18314
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
18315
|
+
*
|
|
18316
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18317
|
+
*/
|
|
18318
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
18319
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
18320
|
+
return progress * progress * (3 - 2 * progress);
|
|
18321
|
+
}
|
|
18322
|
+
/**
|
|
18323
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
18324
|
+
*
|
|
18325
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
18326
|
+
*/
|
|
18327
|
+
function formatAlphaHex(opacity) {
|
|
18328
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
18329
|
+
.toString(16)
|
|
18330
|
+
.padStart(2, '0');
|
|
18331
|
+
}
|
|
18332
|
+
|
|
16780
18333
|
/* eslint-disable no-magic-numbers */
|
|
16781
18334
|
/**
|
|
16782
18335
|
* Octopus avatar visual.
|
|
@@ -17547,6 +19100,8 @@
|
|
|
17547
19100
|
octopus3AvatarVisual,
|
|
17548
19101
|
octopus3dAvatarVisual,
|
|
17549
19102
|
octopus3d2AvatarVisual,
|
|
19103
|
+
octopus3d3AvatarVisual,
|
|
19104
|
+
octopus3d4AvatarVisual,
|
|
17550
19105
|
asciiOctopusAvatarVisual,
|
|
17551
19106
|
minecraftAvatarVisual,
|
|
17552
19107
|
minecraft2AvatarVisual,
|
|
@@ -18226,6 +19781,70 @@
|
|
|
18226
19781
|
}
|
|
18227
19782
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
18228
19783
|
|
|
19784
|
+
/**
|
|
19785
|
+
* META VISIBILITY commitment definition.
|
|
19786
|
+
*
|
|
19787
|
+
* The `META VISIBILITY` commitment stores whether an agent is public, private, or unlisted.
|
|
19788
|
+
* Agents Server mirrors this value into the database for efficient filtering, but the book
|
|
19789
|
+
* commitment remains the editable source of truth.
|
|
19790
|
+
*
|
|
19791
|
+
* @private Metadata-only commitment used by Agents Server.
|
|
19792
|
+
*/
|
|
19793
|
+
class MetaVisibilityCommitmentDefinition extends BaseCommitmentDefinition {
|
|
19794
|
+
constructor() {
|
|
19795
|
+
super('META VISIBILITY');
|
|
19796
|
+
}
|
|
19797
|
+
/**
|
|
19798
|
+
* Short one-line description of META VISIBILITY.
|
|
19799
|
+
*/
|
|
19800
|
+
get description() {
|
|
19801
|
+
return 'Set whether the agent is private, unlisted, or public.';
|
|
19802
|
+
}
|
|
19803
|
+
/**
|
|
19804
|
+
* Icon for this commitment.
|
|
19805
|
+
*/
|
|
19806
|
+
get icon() {
|
|
19807
|
+
return '👁️';
|
|
19808
|
+
}
|
|
19809
|
+
/**
|
|
19810
|
+
* Markdown documentation for META VISIBILITY commitment.
|
|
19811
|
+
*/
|
|
19812
|
+
get documentation() {
|
|
19813
|
+
return _spaceTrim.spaceTrim(`
|
|
19814
|
+
# META VISIBILITY
|
|
19815
|
+
|
|
19816
|
+
Sets the agent visibility used by Agents Server.
|
|
19817
|
+
|
|
19818
|
+
## Allowed values
|
|
19819
|
+
|
|
19820
|
+
- \`PRIVATE\` - accessible only to signed-in users with access.
|
|
19821
|
+
- \`UNLISTED\` - accessible by direct link but hidden from public listings.
|
|
19822
|
+
- \`PUBLIC\` - visible in public listings and accessible by anyone.
|
|
19823
|
+
|
|
19824
|
+
## Key aspects
|
|
19825
|
+
|
|
19826
|
+
- Does not modify the agent's behavior, system message, or tools.
|
|
19827
|
+
- Whitespace and letter case are normalized when persisted.
|
|
19828
|
+
- If multiple \`META VISIBILITY\` commitments are present, persistence keeps one normalized value.
|
|
19829
|
+
- Agents Server mirrors the value into the database for filtering, but the book is the source of truth.
|
|
19830
|
+
|
|
19831
|
+
## Example
|
|
19832
|
+
|
|
19833
|
+
\`\`\`book
|
|
19834
|
+
Helpful Assistant
|
|
19835
|
+
|
|
19836
|
+
GOAL Be helpful and friendly.
|
|
19837
|
+
META VISIBILITY PUBLIC
|
|
19838
|
+
\`\`\`
|
|
19839
|
+
`);
|
|
19840
|
+
}
|
|
19841
|
+
applyToAgentModelRequirements(requirements, content) {
|
|
19842
|
+
// META VISIBILITY is metadata only and does not alter model requirements.
|
|
19843
|
+
return requirements;
|
|
19844
|
+
}
|
|
19845
|
+
}
|
|
19846
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
19847
|
+
|
|
18229
19848
|
/**
|
|
18230
19849
|
* META VOICE commitment definition
|
|
18231
19850
|
*
|
|
@@ -19835,7 +21454,15 @@
|
|
|
19835
21454
|
* Builds the teammate request text, optionally including context.
|
|
19836
21455
|
*/
|
|
19837
21456
|
function buildTeammateRequest(message, context) {
|
|
19838
|
-
|
|
21457
|
+
if (!context) {
|
|
21458
|
+
return message;
|
|
21459
|
+
}
|
|
21460
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
21461
|
+
${block(message)}
|
|
21462
|
+
|
|
21463
|
+
Context:
|
|
21464
|
+
${block(context)}
|
|
21465
|
+
`);
|
|
19839
21466
|
}
|
|
19840
21467
|
/**
|
|
19841
21468
|
* Builds a minimal chat prompt for teammate calls.
|
|
@@ -23824,7 +25451,11 @@
|
|
|
23824
25451
|
const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
|
|
23825
25452
|
const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
|
|
23826
25453
|
const contentToReturn = wasCharacterTruncated
|
|
23827
|
-
?
|
|
25454
|
+
? _spaceTrim.spaceTrim((block) => `
|
|
25455
|
+
${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
|
|
25456
|
+
|
|
25457
|
+
[...truncated...]
|
|
25458
|
+
`)
|
|
23828
25459
|
: lineRangedContent;
|
|
23829
25460
|
const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
|
|
23830
25461
|
if (wasCharacterTruncated) {
|
|
@@ -24600,6 +26231,40 @@
|
|
|
24600
26231
|
}
|
|
24601
26232
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
24602
26233
|
|
|
26234
|
+
/**
|
|
26235
|
+
* Supported visibility states for persisted agents.
|
|
26236
|
+
*
|
|
26237
|
+
* @public exported from `@promptbook/core`
|
|
26238
|
+
*/
|
|
26239
|
+
const AGENT_VISIBILITY_VALUES = ['PRIVATE', 'UNLISTED', 'PUBLIC'];
|
|
26240
|
+
/**
|
|
26241
|
+
* Returns `true` when the value is one of supported visibility states.
|
|
26242
|
+
*
|
|
26243
|
+
* @param value - Raw value to validate.
|
|
26244
|
+
* @returns Whether the value is a valid `AgentVisibility`.
|
|
26245
|
+
*
|
|
26246
|
+
* @public exported from `@promptbook/core`
|
|
26247
|
+
*/
|
|
26248
|
+
function isAgentVisibility(value) {
|
|
26249
|
+
return typeof value === 'string' && AGENT_VISIBILITY_VALUES.includes(value);
|
|
26250
|
+
}
|
|
26251
|
+
/**
|
|
26252
|
+
* Normalizes raw visibility text into a supported value.
|
|
26253
|
+
*
|
|
26254
|
+
* @param value - Raw visibility value.
|
|
26255
|
+
* @returns Normalized visibility, or `null` when invalid.
|
|
26256
|
+
*
|
|
26257
|
+
* @public exported from `@promptbook/core`
|
|
26258
|
+
*/
|
|
26259
|
+
function normalizeAgentVisibility(value) {
|
|
26260
|
+
if (typeof value !== 'string') {
|
|
26261
|
+
return null;
|
|
26262
|
+
}
|
|
26263
|
+
const normalized = value.trim().toUpperCase();
|
|
26264
|
+
return isAgentVisibility(normalized) ? normalized : null;
|
|
26265
|
+
}
|
|
26266
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
26267
|
+
|
|
24603
26268
|
/**
|
|
24604
26269
|
* Maximum allowed source length for create-agent payloads.
|
|
24605
26270
|
*
|
|
@@ -26900,6 +28565,7 @@
|
|
|
26900
28565
|
new MetaDomainCommitmentDefinition(),
|
|
26901
28566
|
new MetaDisclaimerCommitmentDefinition(),
|
|
26902
28567
|
new MetaInputPlaceholderCommitmentDefinition(),
|
|
28568
|
+
new MetaVisibilityCommitmentDefinition(),
|
|
26903
28569
|
new MetaCommitmentDefinition(),
|
|
26904
28570
|
new MetaVoiceCommitmentDefinition(),
|
|
26905
28571
|
new NoteCommitmentDefinition('NOTE'),
|
|
@@ -31942,6 +33608,7 @@
|
|
|
31942
33608
|
'META COLOR': applyMetaColorContent,
|
|
31943
33609
|
'META FONT': applyMetaFontContent,
|
|
31944
33610
|
'META VOICE': applyMetaVoiceContent,
|
|
33611
|
+
'META VISIBILITY': applyMetaVisibilityContent,
|
|
31945
33612
|
};
|
|
31946
33613
|
/**
|
|
31947
33614
|
* Applies META-style commitments that mutate parsed profile metadata.
|
|
@@ -31971,6 +33638,10 @@
|
|
|
31971
33638
|
applyMetaAvatarContent(state, metaValue);
|
|
31972
33639
|
return;
|
|
31973
33640
|
}
|
|
33641
|
+
if (metaTypeRaw.toUpperCase() === 'VISIBILITY') {
|
|
33642
|
+
applyMetaVisibilityContent(state, metaValue);
|
|
33643
|
+
return;
|
|
33644
|
+
}
|
|
31974
33645
|
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
31975
33646
|
state.meta[metaType] = metaValue;
|
|
31976
33647
|
}
|
|
@@ -32047,6 +33718,15 @@
|
|
|
32047
33718
|
function applyMetaVoiceContent(state, content) {
|
|
32048
33719
|
state.meta.voice = _spaceTrim.spaceTrim(content);
|
|
32049
33720
|
}
|
|
33721
|
+
/**
|
|
33722
|
+
* Applies META VISIBILITY content into the normalized `meta.visibility` field.
|
|
33723
|
+
*/
|
|
33724
|
+
function applyMetaVisibilityContent(state, content) {
|
|
33725
|
+
const visibility = normalizeAgentVisibility(content);
|
|
33726
|
+
if (visibility) {
|
|
33727
|
+
state.meta.visibility = visibility;
|
|
33728
|
+
}
|
|
33729
|
+
}
|
|
32050
33730
|
/**
|
|
32051
33731
|
* Normalizes the separator in the content
|
|
32052
33732
|
*
|
|
@@ -32759,11 +34439,19 @@
|
|
|
32759
34439
|
try {
|
|
32760
34440
|
const json = JSON.parse(content);
|
|
32761
34441
|
const formattedJson = JSON.stringify(json, null, 4);
|
|
32762
|
-
return
|
|
34442
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
34443
|
+
\`\`\`json
|
|
34444
|
+
${block(formattedJson)}
|
|
34445
|
+
\`\`\`
|
|
34446
|
+
`);
|
|
32763
34447
|
}
|
|
32764
34448
|
catch (error) {
|
|
32765
34449
|
// If JSON is invalid, still import it but maybe not as pretty JSON
|
|
32766
|
-
return
|
|
34450
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
34451
|
+
\`\`\`json
|
|
34452
|
+
${block(content)}
|
|
34453
|
+
\`\`\`
|
|
34454
|
+
`);
|
|
32767
34455
|
}
|
|
32768
34456
|
},
|
|
32769
34457
|
};
|
|
@@ -32787,7 +34475,11 @@
|
|
|
32787
34475
|
import(content, mimeType) {
|
|
32788
34476
|
const extension = mimeTypeToExtension(mimeType);
|
|
32789
34477
|
const codeBlockType = extension || 'txt';
|
|
32790
|
-
return
|
|
34478
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
34479
|
+
\`\`\`${codeBlockType}
|
|
34480
|
+
${block(content)}
|
|
34481
|
+
\`\`\`
|
|
34482
|
+
`);
|
|
32791
34483
|
},
|
|
32792
34484
|
};
|
|
32793
34485
|
|
|
@@ -33034,11 +34726,19 @@
|
|
|
33034
34726
|
const examples = [];
|
|
33035
34727
|
const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
|
|
33036
34728
|
if (initialMessage) {
|
|
33037
|
-
examples.push(
|
|
34729
|
+
examples.push(_spaceTrim.spaceTrim((block) => `
|
|
34730
|
+
**Agent:**
|
|
34731
|
+
${block(initialMessage)}
|
|
34732
|
+
`));
|
|
33038
34733
|
}
|
|
33039
34734
|
if (samples && samples.length > 0) {
|
|
33040
34735
|
for (const sample of samples) {
|
|
33041
|
-
examples.push(
|
|
34736
|
+
examples.push(_spaceTrim.spaceTrim((block) => `
|
|
34737
|
+
**User:** ${block(String(sample.question))}
|
|
34738
|
+
|
|
34739
|
+
**Agent:**
|
|
34740
|
+
${block(sample.answer)}
|
|
34741
|
+
`));
|
|
33042
34742
|
}
|
|
33043
34743
|
}
|
|
33044
34744
|
return examples;
|
|
@@ -34360,7 +36060,9 @@
|
|
|
34360
36060
|
if (!executionTools || !executionTools.script) {
|
|
34361
36061
|
throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
|
|
34362
36062
|
}
|
|
34363
|
-
return Array.isArray(executionTools.script)
|
|
36063
|
+
return Array.isArray(executionTools.script)
|
|
36064
|
+
? executionTools.script
|
|
36065
|
+
: [executionTools.script];
|
|
34364
36066
|
}
|
|
34365
36067
|
/**
|
|
34366
36068
|
* Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
|
|
@@ -37422,7 +39124,7 @@
|
|
|
37422
39124
|
return { skippedReason: 'invalid_data_url' };
|
|
37423
39125
|
}
|
|
37424
39126
|
return {
|
|
37425
|
-
file: new File([parsed.buffer], parsed.filename, {
|
|
39127
|
+
file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
|
|
37426
39128
|
type: parsed.mimeType,
|
|
37427
39129
|
}),
|
|
37428
39130
|
sizeBytes: parsed.buffer.length,
|
|
@@ -39170,7 +40872,7 @@
|
|
|
39170
40872
|
* Computes one stable hash from a JSON-serializable value.
|
|
39171
40873
|
*/
|
|
39172
40874
|
function computeJsonHash$1(value) {
|
|
39173
|
-
return
|
|
40875
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
39174
40876
|
}
|
|
39175
40877
|
/**
|
|
39176
40878
|
* Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
|
|
@@ -39328,7 +41030,7 @@
|
|
|
39328
41030
|
* Computes one stable hash from a JSON-serializable value.
|
|
39329
41031
|
*/
|
|
39330
41032
|
function computeJsonHash(value) {
|
|
39331
|
-
return
|
|
41033
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
39332
41034
|
}
|
|
39333
41035
|
/**
|
|
39334
41036
|
* Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
|
|
@@ -39664,7 +41366,11 @@
|
|
|
39664
41366
|
};
|
|
39665
41367
|
}
|
|
39666
41368
|
return {
|
|
39667
|
-
content:
|
|
41369
|
+
content: _spaceTrim.spaceTrim((block) => `
|
|
41370
|
+
${block(content.slice(0, Math.max(0, maxCharacters)))}
|
|
41371
|
+
|
|
41372
|
+
[...truncated...]
|
|
41373
|
+
`),
|
|
39668
41374
|
isTruncated: true,
|
|
39669
41375
|
};
|
|
39670
41376
|
}
|
|
@@ -40130,7 +41836,7 @@
|
|
|
40130
41836
|
* Returns a virtual model name representing the agent behavior.
|
|
40131
41837
|
*/
|
|
40132
41838
|
get modelName() {
|
|
40133
|
-
const hash =
|
|
41839
|
+
const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
|
|
40134
41840
|
const agentId = hash.substring(0, 10);
|
|
40135
41841
|
return (normalizeToKebabCase(this.title) + '-' + agentId);
|
|
40136
41842
|
}
|