@promptbook/remote-server 0.112.0-98 → 0.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -21
- package/esm/index.es.js +1688 -98
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/components.index.d.ts +4 -0
- package/esm/src/_packages/core.index.d.ts +0 -2
- package/esm/src/_packages/node.index.d.ts +40 -0
- package/esm/src/_packages/types.index.d.ts +18 -0
- package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/esm/src/book-3.0/CliAgent.d.ts +66 -0
- package/esm/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/LiteAgent.d.ts +68 -0
- package/esm/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/esm/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/esm/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/esm/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
- package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/esm/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/esm/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/esm/src/cli/cli-commands/agent.d.ts +14 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +17 -1
- package/esm/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/esm/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +1689 -96
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/components.index.d.ts +4 -0
- package/umd/src/_packages/core.index.d.ts +0 -2
- package/umd/src/_packages/node.index.d.ts +40 -0
- package/umd/src/_packages/types.index.d.ts +18 -0
- package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/umd/src/book-3.0/CliAgent.d.ts +66 -0
- package/umd/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/LiteAgent.d.ts +68 -0
- package/umd/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/umd/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/umd/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/umd/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +1 -0
- package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/umd/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/umd/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/umd/src/cli/cli-commands/agent.d.ts +14 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +17 -1
- package/umd/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/umd/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -0
- package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/umd/src/version.d.ts +1 -1
package/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.112.0
|
|
56
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.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,
|
|
@@ -19835,7 +21390,15 @@
|
|
|
19835
21390
|
* Builds the teammate request text, optionally including context.
|
|
19836
21391
|
*/
|
|
19837
21392
|
function buildTeammateRequest(message, context) {
|
|
19838
|
-
|
|
21393
|
+
if (!context) {
|
|
21394
|
+
return message;
|
|
21395
|
+
}
|
|
21396
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
21397
|
+
${block(message)}
|
|
21398
|
+
|
|
21399
|
+
Context:
|
|
21400
|
+
${block(context)}
|
|
21401
|
+
`);
|
|
19839
21402
|
}
|
|
19840
21403
|
/**
|
|
19841
21404
|
* Builds a minimal chat prompt for teammate calls.
|
|
@@ -23824,7 +25387,11 @@
|
|
|
23824
25387
|
const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
|
|
23825
25388
|
const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
|
|
23826
25389
|
const contentToReturn = wasCharacterTruncated
|
|
23827
|
-
?
|
|
25390
|
+
? _spaceTrim.spaceTrim((block) => `
|
|
25391
|
+
${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
|
|
25392
|
+
|
|
25393
|
+
[...truncated...]
|
|
25394
|
+
`)
|
|
23828
25395
|
: lineRangedContent;
|
|
23829
25396
|
const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
|
|
23830
25397
|
if (wasCharacterTruncated) {
|
|
@@ -32759,11 +34326,19 @@
|
|
|
32759
34326
|
try {
|
|
32760
34327
|
const json = JSON.parse(content);
|
|
32761
34328
|
const formattedJson = JSON.stringify(json, null, 4);
|
|
32762
|
-
return
|
|
34329
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
34330
|
+
\`\`\`json
|
|
34331
|
+
${block(formattedJson)}
|
|
34332
|
+
\`\`\`
|
|
34333
|
+
`);
|
|
32763
34334
|
}
|
|
32764
34335
|
catch (error) {
|
|
32765
34336
|
// If JSON is invalid, still import it but maybe not as pretty JSON
|
|
32766
|
-
return
|
|
34337
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
34338
|
+
\`\`\`json
|
|
34339
|
+
${block(content)}
|
|
34340
|
+
\`\`\`
|
|
34341
|
+
`);
|
|
32767
34342
|
}
|
|
32768
34343
|
},
|
|
32769
34344
|
};
|
|
@@ -32787,7 +34362,11 @@
|
|
|
32787
34362
|
import(content, mimeType) {
|
|
32788
34363
|
const extension = mimeTypeToExtension(mimeType);
|
|
32789
34364
|
const codeBlockType = extension || 'txt';
|
|
32790
|
-
return
|
|
34365
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
34366
|
+
\`\`\`${codeBlockType}
|
|
34367
|
+
${block(content)}
|
|
34368
|
+
\`\`\`
|
|
34369
|
+
`);
|
|
32791
34370
|
},
|
|
32792
34371
|
};
|
|
32793
34372
|
|
|
@@ -33034,11 +34613,19 @@
|
|
|
33034
34613
|
const examples = [];
|
|
33035
34614
|
const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
|
|
33036
34615
|
if (initialMessage) {
|
|
33037
|
-
examples.push(
|
|
34616
|
+
examples.push(_spaceTrim.spaceTrim((block) => `
|
|
34617
|
+
**Agent:**
|
|
34618
|
+
${block(initialMessage)}
|
|
34619
|
+
`));
|
|
33038
34620
|
}
|
|
33039
34621
|
if (samples && samples.length > 0) {
|
|
33040
34622
|
for (const sample of samples) {
|
|
33041
|
-
examples.push(
|
|
34623
|
+
examples.push(_spaceTrim.spaceTrim((block) => `
|
|
34624
|
+
**User:** ${block(String(sample.question))}
|
|
34625
|
+
|
|
34626
|
+
**Agent:**
|
|
34627
|
+
${block(sample.answer)}
|
|
34628
|
+
`));
|
|
33042
34629
|
}
|
|
33043
34630
|
}
|
|
33044
34631
|
return examples;
|
|
@@ -34360,7 +35947,9 @@
|
|
|
34360
35947
|
if (!executionTools || !executionTools.script) {
|
|
34361
35948
|
throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
|
|
34362
35949
|
}
|
|
34363
|
-
return Array.isArray(executionTools.script)
|
|
35950
|
+
return Array.isArray(executionTools.script)
|
|
35951
|
+
? executionTools.script
|
|
35952
|
+
: [executionTools.script];
|
|
34364
35953
|
}
|
|
34365
35954
|
/**
|
|
34366
35955
|
* Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
|
|
@@ -37422,7 +39011,7 @@
|
|
|
37422
39011
|
return { skippedReason: 'invalid_data_url' };
|
|
37423
39012
|
}
|
|
37424
39013
|
return {
|
|
37425
|
-
file: new File([parsed.buffer], parsed.filename, {
|
|
39014
|
+
file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
|
|
37426
39015
|
type: parsed.mimeType,
|
|
37427
39016
|
}),
|
|
37428
39017
|
sizeBytes: parsed.buffer.length,
|
|
@@ -39170,7 +40759,7 @@
|
|
|
39170
40759
|
* Computes one stable hash from a JSON-serializable value.
|
|
39171
40760
|
*/
|
|
39172
40761
|
function computeJsonHash$1(value) {
|
|
39173
|
-
return
|
|
40762
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
39174
40763
|
}
|
|
39175
40764
|
/**
|
|
39176
40765
|
* Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
|
|
@@ -39328,7 +40917,7 @@
|
|
|
39328
40917
|
* Computes one stable hash from a JSON-serializable value.
|
|
39329
40918
|
*/
|
|
39330
40919
|
function computeJsonHash(value) {
|
|
39331
|
-
return
|
|
40920
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
39332
40921
|
}
|
|
39333
40922
|
/**
|
|
39334
40923
|
* Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
|
|
@@ -39664,7 +41253,11 @@
|
|
|
39664
41253
|
};
|
|
39665
41254
|
}
|
|
39666
41255
|
return {
|
|
39667
|
-
content:
|
|
41256
|
+
content: _spaceTrim.spaceTrim((block) => `
|
|
41257
|
+
${block(content.slice(0, Math.max(0, maxCharacters)))}
|
|
41258
|
+
|
|
41259
|
+
[...truncated...]
|
|
41260
|
+
`),
|
|
39668
41261
|
isTruncated: true,
|
|
39669
41262
|
};
|
|
39670
41263
|
}
|
|
@@ -40130,7 +41723,7 @@
|
|
|
40130
41723
|
* Returns a virtual model name representing the agent behavior.
|
|
40131
41724
|
*/
|
|
40132
41725
|
get modelName() {
|
|
40133
|
-
const hash =
|
|
41726
|
+
const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
|
|
40134
41727
|
const agentId = hash.substring(0, 10);
|
|
40135
41728
|
return (normalizeToKebabCase(this.title) + '-' + agentId);
|
|
40136
41729
|
}
|