@promptbook/wizard 0.112.0-99 → 0.113.0-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -44
- package/esm/index.es.js +1813 -103
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/components.index.d.ts +4 -0
- package/esm/src/_packages/core.index.d.ts +22 -2
- package/esm/src/_packages/node.index.d.ts +40 -0
- package/esm/src/_packages/types.index.d.ts +20 -0
- package/esm/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/esm/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/esm/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/esm/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/esm/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/esm/src/book-3.0/CliAgent.d.ts +66 -0
- package/esm/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/LiteAgent.d.ts +68 -0
- package/esm/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/esm/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/esm/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/esm/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/esm/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/esm/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/esm/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/esm/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
- package/esm/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/esm/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/esm/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/esm/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/esm/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/esm/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/esm/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/esm/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/esm/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/esm/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/esm/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/esm/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/esm/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/esm/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/esm/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/esm/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/esm/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/esm/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/esm/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/esm/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/esm/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/esm/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/esm/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/esm/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/esm/src/cli/cli-commands/agent.d.ts +14 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
- package/esm/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/esm/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/esm/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/esm/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/esm/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/esm/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/esm/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/esm/src/commitments/index.d.ts +2 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/esm/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/esm/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -0
- package/esm/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +1814 -101
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/components.index.d.ts +4 -0
- package/umd/src/_packages/core.index.d.ts +22 -2
- package/umd/src/_packages/node.index.d.ts +40 -0
- package/umd/src/_packages/types.index.d.ts +20 -0
- package/umd/src/avatars/avatarAnimationScheduler.d.ts +4 -0
- package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
- package/umd/src/avatars/visuals/octopus3d3AvatarVisual.d.ts +7 -0
- package/umd/src/avatars/visuals/octopus3d4AvatarVisual.d.ts +7 -0
- package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +2 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.d.ts +97 -0
- package/umd/src/book-2.0/agent-source/agentSourceVisibility.test.d.ts +1 -0
- package/umd/src/book-3.0/BookNodeAgentSource.d.ts +38 -0
- package/umd/src/book-3.0/CliAgent.d.ts +66 -0
- package/umd/src/book-3.0/CliAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/LiteAgent.d.ts +68 -0
- package/umd/src/book-3.0/LiteAgent.test.d.ts +1 -0
- package/umd/src/book-3.0/agentFolderPaths.d.ts +30 -0
- package/umd/src/book-3.0/cliAgentEnv.d.ts +33 -0
- package/umd/src/book-components/BookEditor/BookEditor.d.ts +6 -5
- package/umd/src/book-components/BookEditor/BookEditorAboutPromptbookInformation.d.ts +12 -0
- package/umd/src/book-components/BookEditor/BookEditorBrowserConfig.d.ts +2 -0
- package/umd/src/book-components/BookEditor/BookEditorForClient.d.ts +7 -0
- package/umd/src/book-components/BookEditor/BookEditorMonacoTokenization.d.ts +2 -0
- package/umd/src/book-components/BookEditor/BookEditorTheme.d.ts +24 -0
- package/umd/src/book-components/BookEditor/createDeprecatedCommitmentDiagnostics.browser.d.ts +9 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLanguage.d.ts +1 -6
- package/umd/src/book-components/BookEditor/useBookEditorMonacoLifecycle.d.ts +1 -4
- package/umd/src/book-components/BookEditor/useBookEditorMonacoStyles.d.ts +2 -1
- package/umd/src/book-components/Chat/Chat/ChatCitationModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModal.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatToolCallModalContent.d.ts +3 -1
- package/umd/src/book-components/Chat/Chat/CitationIframePreview.d.ts +20 -0
- package/umd/src/book-components/Chat/Chat/TeamToolCallModalContent.d.ts +2 -0
- package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/umd/src/book-components/Chat/SourceChip/SourceChip.d.ts +6 -1
- package/umd/src/book-components/Chat/hooks/useResolvedCitationLabel.d.ts +12 -0
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +4 -0
- package/umd/src/book-components/Chat/types/ChatParticipant.d.ts +1 -1
- package/umd/src/book-components/Chat/types/CitationLabelResolver.d.ts +8 -0
- package/umd/src/book-components/Chat/utils/citationHelpers.d.ts +9 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.d.ts +14 -0
- package/umd/src/book-components/Chat/utils/decodeJsonUnicodeEscapesInMarkdownText.test.d.ts +1 -0
- package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +10 -0
- package/umd/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +4 -0
- package/umd/src/book-components/_common/Dropdown/Dropdown.d.ts +1 -1
- package/umd/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +1 -1
- package/umd/src/book-components/_common/Modal/Modal.d.ts +1 -1
- package/umd/src/book-components/icons/AboutIcon.d.ts +1 -1
- package/umd/src/book-components/icons/DownloadIcon.d.ts +1 -1
- package/umd/src/book-components/icons/ExitFullscreenIcon.d.ts +1 -1
- package/umd/src/book-components/icons/FullscreenIcon.d.ts +1 -1
- package/umd/src/cli/cli-commands/agent/agentCliOptions.d.ts +38 -0
- package/umd/src/cli/cli-commands/agent/chat.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/exec.d.ts +10 -0
- package/umd/src/cli/cli-commands/agent/run.test.d.ts +1 -0
- package/umd/src/cli/cli-commands/agent-folder/agentProjectPaths.d.ts +2 -24
- package/umd/src/cli/cli-commands/agent.d.ts +14 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +23 -1
- package/umd/src/cli/cli-commands/agents-server/run.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +9 -2
- package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/umd/src/cli/cli-commands/coder/ensureCoderDeveloperAgentFile.d.ts +25 -0
- package/umd/src/cli/cli-commands/coder/find-unwritten.d.ts +10 -0
- package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +2 -0
- package/umd/src/cli/cli-commands/coder/server.d.ts +13 -0
- package/umd/src/cli/cli-commands/coder/waitOptions.d.ts +14 -0
- package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +9 -25
- package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +2 -1
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -0
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +21 -1
- package/umd/src/commitments/META_VISIBILITY/META_VISIBILITY.d.ts +27 -0
- package/umd/src/commitments/_common/teamInternalAgentAccess.d.ts +9 -1
- package/umd/src/commitments/index.d.ts +2 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
- package/umd/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
- package/umd/src/scrapers/website/utils/createShowdownConverter.d.ts +2 -2
- package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -0
- package/umd/src/utils/validators/url/isValidAgentUrl.d.ts +5 -0
- package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
2
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('socket.io-client'), require('spacetrim'), require('@anthropic-ai/sdk'), require('bottleneck'), require('colors'), require('@azure/openai'), require('fs/promises'), require('child_process'), require('waitasecond'), require('crypto-js'), require('crypto-js/enc-hex'), require('path'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('dotenv'), require('crypto-js/sha256'), require('jszip'), require('rxjs'), require('crypto'), require('moment'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
|
|
3
3
|
typeof define === 'function' && define.amd ? define(['exports', 'socket.io-client', 'spacetrim', '@anthropic-ai/sdk', 'bottleneck', 'colors', '@azure/openai', 'fs/promises', 'child_process', 'waitasecond', 'crypto-js', 'crypto-js/enc-hex', 'path', '@mozilla/readability', 'jsdom', 'showdown', 'dotenv', 'crypto-js/sha256', 'jszip', 'rxjs', 'crypto', 'moment', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-wizard"] = {}, global.socket_ioClient, global._spaceTrim, global.Anthropic, global.Bottleneck, global.colors, global.openai, global.promises, global.child_process, global.waitasecond, global.
|
|
5
|
-
})(this, (function (exports, socket_ioClient, _spaceTrim, Anthropic, Bottleneck, colors, openai, promises, child_process, waitasecond,
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-wizard"] = {}, global.socket_ioClient, global._spaceTrim, global.Anthropic, global.Bottleneck, global.colors, global.openai, global.promises, global.child_process, global.waitasecond, global.CryptoJS, global.hexEncoder, global.path, global.readability, global.jsdom, global.showdown, global.dotenv, global.sha256, global.JSZip, global.rxjs, null, global.moment, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
|
|
5
|
+
})(this, (function (exports, socket_ioClient, _spaceTrim, Anthropic, Bottleneck, colors, openai, promises, child_process, waitasecond, CryptoJS, hexEncoder, path, readability, jsdom, showdown, dotenv, sha256, JSZip, rxjs, crypto, moment, mimeTypes, papaparse, agents, 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 Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
|
|
29
29
|
var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
|
|
30
30
|
var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
|
|
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 dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
|
|
33
35
|
var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
|
|
34
36
|
var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
|
|
35
37
|
var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
|
|
38
|
+
var papaparse__default = /*#__PURE__*/_interopDefaultLegacy(papaparse);
|
|
36
39
|
var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
|
|
37
40
|
|
|
38
41
|
// ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
|
|
@@ -49,7 +52,7 @@
|
|
|
49
52
|
* @generated
|
|
50
53
|
* @see https://github.com/webgptorg/promptbook
|
|
51
54
|
*/
|
|
52
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.
|
|
55
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.113.0-0';
|
|
53
56
|
/**
|
|
54
57
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
55
58
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -2104,7 +2107,7 @@
|
|
|
2104
2107
|
title: 'Anthropic Claude',
|
|
2105
2108
|
packageName: '@promptbook/anthropic-claude',
|
|
2106
2109
|
className: 'AnthropicClaudeExecutionTools',
|
|
2107
|
-
envVariables: ['ANTHROPIC_CLAUDE_API_KEY'],
|
|
2110
|
+
envVariables: ['ANTHROPIC_API_KEY', 'ANTHROPIC_CLAUDE_API_KEY'],
|
|
2108
2111
|
trustLevel: 'CLOSED',
|
|
2109
2112
|
order: MODEL_ORDERS.TOP_TIER,
|
|
2110
2113
|
getBoilerplateConfiguration() {
|
|
@@ -2121,14 +2124,17 @@
|
|
|
2121
2124
|
};
|
|
2122
2125
|
},
|
|
2123
2126
|
createConfigurationFromEnv(env) {
|
|
2127
|
+
var _a;
|
|
2124
2128
|
// Note: Note using `process.env` BUT `env` to pass in the environment variables dynamically
|
|
2125
|
-
|
|
2129
|
+
// Note: `ANTHROPIC_API_KEY` takes precedence over the deprecated `ANTHROPIC_CLAUDE_API_KEY`
|
|
2130
|
+
const apiKey = (_a = env.ANTHROPIC_API_KEY) !== null && _a !== void 0 ? _a : env.ANTHROPIC_CLAUDE_API_KEY;
|
|
2131
|
+
if (typeof apiKey === 'string') {
|
|
2126
2132
|
return {
|
|
2127
2133
|
title: 'Claude (from env)',
|
|
2128
2134
|
packageName: '@promptbook/anthropic-claude',
|
|
2129
2135
|
className: 'AnthropicClaudeExecutionTools',
|
|
2130
2136
|
options: {
|
|
2131
|
-
apiKey
|
|
2137
|
+
apiKey,
|
|
2132
2138
|
},
|
|
2133
2139
|
};
|
|
2134
2140
|
}
|
|
@@ -2357,7 +2363,7 @@
|
|
|
2357
2363
|
* @private internal helper function
|
|
2358
2364
|
*/
|
|
2359
2365
|
function $randomToken(randomness) {
|
|
2360
|
-
return
|
|
2366
|
+
return CryptoJS__default["default"].lib.WordArray.random(randomness).toString(CryptoJS__default["default"].enc.Hex);
|
|
2361
2367
|
}
|
|
2362
2368
|
// TODO: [🤶] Maybe export through `@promptbook/utils` or `@promptbook/random` package
|
|
2363
2369
|
// TODO: Maybe use nanoid instead https://github.com/ai/nanoid
|
|
@@ -10031,7 +10037,7 @@
|
|
|
10031
10037
|
return { skippedReason: 'invalid_data_url' };
|
|
10032
10038
|
}
|
|
10033
10039
|
return {
|
|
10034
|
-
file: new File([parsed.buffer], parsed.filename, {
|
|
10040
|
+
file: new File([new Uint8Array(parsed.buffer)], parsed.filename, {
|
|
10035
10041
|
type: parsed.mimeType,
|
|
10036
10042
|
}),
|
|
10037
10043
|
sizeBytes: parsed.buffer.length,
|
|
@@ -13312,7 +13318,7 @@
|
|
|
13312
13318
|
* @public exported from `@promptbook/editable`
|
|
13313
13319
|
*/
|
|
13314
13320
|
function knowledgeSourceContentToName(knowledgeSourceContent) {
|
|
13315
|
-
const hash =
|
|
13321
|
+
const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(JSON.stringify(knowledgeSourceContent)))
|
|
13316
13322
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
13317
13323
|
.toString( /* hex */)
|
|
13318
13324
|
.substring(0, 20);
|
|
@@ -14192,7 +14198,7 @@
|
|
|
14192
14198
|
console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
|
|
14193
14199
|
value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
14194
14200
|
}
|
|
14195
|
-
const csv =
|
|
14201
|
+
const csv = papaparse__default["default"].parse(value, settings);
|
|
14196
14202
|
return csv;
|
|
14197
14203
|
}
|
|
14198
14204
|
|
|
@@ -14277,10 +14283,10 @@
|
|
|
14277
14283
|
i > index ? { ...row, [outputParameterName]: PENDING_VALUE_PLACEHOLDER } : row,
|
|
14278
14284
|
);
|
|
14279
14285
|
*/
|
|
14280
|
-
await onProgress(
|
|
14286
|
+
await onProgress(papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS }));
|
|
14281
14287
|
}
|
|
14282
14288
|
}
|
|
14283
|
-
return
|
|
14289
|
+
return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
14284
14290
|
},
|
|
14285
14291
|
},
|
|
14286
14292
|
{
|
|
@@ -14308,7 +14314,7 @@
|
|
|
14308
14314
|
return /* not await */ mapCallback({ [key]: value }, index, array.length);
|
|
14309
14315
|
}));
|
|
14310
14316
|
}));
|
|
14311
|
-
return
|
|
14317
|
+
return papaparse__default["default"].unparse(mappedData, { ...settings, ...MANDATORY_CSV_SETTINGS });
|
|
14312
14318
|
},
|
|
14313
14319
|
},
|
|
14314
14320
|
],
|
|
@@ -16324,7 +16330,7 @@
|
|
|
16324
16330
|
const { filename: sourceFilename, url } = source;
|
|
16325
16331
|
const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
|
|
16326
16332
|
// TODO: [👬] DRY
|
|
16327
|
-
const hash =
|
|
16333
|
+
const hash = CryptoJS__default["default"].SHA256(
|
|
16328
16334
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
16329
16335
|
hexEncoder__default["default"].parse(sourceFilename || url || 'untitled'))
|
|
16330
16336
|
.toString( /* hex */)
|
|
@@ -17227,7 +17233,7 @@
|
|
|
17227
17233
|
* @private utility of `WebsiteScraper`
|
|
17228
17234
|
*/
|
|
17229
17235
|
function createShowdownConverter() {
|
|
17230
|
-
return new
|
|
17236
|
+
return new showdown__default["default"].Converter({
|
|
17231
17237
|
flavor: 'github',
|
|
17232
17238
|
/*
|
|
17233
17239
|
> import showdownHighlight from 'showdown-highlight';
|
|
@@ -17411,7 +17417,7 @@
|
|
|
17411
17417
|
* @public exported from `@promptbook/utils`
|
|
17412
17418
|
*/
|
|
17413
17419
|
function computeHash(value) {
|
|
17414
|
-
return
|
|
17420
|
+
return CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(_spaceTrim.spaceTrim(valueToString(value)))).toString( /* hex */);
|
|
17415
17421
|
}
|
|
17416
17422
|
// TODO: [🥬][🥬] Use this ACRY
|
|
17417
17423
|
|
|
@@ -19180,6 +19186,11 @@
|
|
|
19180
19186
|
* - `isValidAgentUrl` *(this one)* which tests just agent URL
|
|
19181
19187
|
* - `isValidPipelineUrl` which tests just pipeline URL
|
|
19182
19188
|
*
|
|
19189
|
+
* Note: This is a pure structural validator and does not block private/internal network
|
|
19190
|
+
* addresses. Callers that fetch the URL server-side from an untrusted network context must
|
|
19191
|
+
* additionally apply the `assertSafeUrl` SSRF guard from the Agents Server before any
|
|
19192
|
+
* outbound request.
|
|
19193
|
+
*
|
|
19183
19194
|
* @public exported from `@promptbook/utils`
|
|
19184
19195
|
*/
|
|
19185
19196
|
function isValidAgentUrl(url) {
|
|
@@ -19193,12 +19204,6 @@
|
|
|
19193
19204
|
// TODO: [🐠]
|
|
19194
19205
|
return false;
|
|
19195
19206
|
}
|
|
19196
|
-
/*
|
|
19197
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
19198
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
19199
|
-
return false;
|
|
19200
|
-
}
|
|
19201
|
-
*/
|
|
19202
19207
|
return true;
|
|
19203
19208
|
}
|
|
19204
19209
|
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
@@ -19374,8 +19379,11 @@
|
|
|
19374
19379
|
if (!trimmedContent) {
|
|
19375
19380
|
return requirements;
|
|
19376
19381
|
}
|
|
19377
|
-
|
|
19378
|
-
|
|
19382
|
+
const goalSection = _spaceTrim.spaceTrim((block) => `
|
|
19383
|
+
## Goal
|
|
19384
|
+
|
|
19385
|
+
${block(trimmedContent)}
|
|
19386
|
+
`);
|
|
19379
19387
|
const requirementsWithGoal = this.appendToSystemMessage(requirements, goalSection, '\n\n');
|
|
19380
19388
|
return this.appendToPromptSuffix(requirementsWithGoal, trimmedContent);
|
|
19381
19389
|
}
|
|
@@ -19761,8 +19769,11 @@
|
|
|
19761
19769
|
if (!trimmedContent) {
|
|
19762
19770
|
return requirements;
|
|
19763
19771
|
}
|
|
19764
|
-
|
|
19765
|
-
|
|
19772
|
+
const languageSection = _spaceTrim.spaceTrim((block) => `
|
|
19773
|
+
## Language
|
|
19774
|
+
|
|
19775
|
+
- Your language is ${block(trimmedContent)}
|
|
19776
|
+
`);
|
|
19766
19777
|
return this.appendToSystemMessage(requirements, languageSection, '\n\n');
|
|
19767
19778
|
}
|
|
19768
19779
|
}
|
|
@@ -22105,21 +22116,22 @@
|
|
|
22105
22116
|
* @private helper of `fractalAvatarVisual`
|
|
22106
22117
|
*/
|
|
22107
22118
|
function drawDragonCurveLayer(context, points, options) {
|
|
22108
|
-
const {
|
|
22119
|
+
const { primaryColor, secondaryColor, tertiaryColor, shadowColor, strokeWidth, timeMs, layerIndex } = options;
|
|
22109
22120
|
const firstPoint = points[0];
|
|
22110
22121
|
const lastPoint = points[points.length - 1];
|
|
22111
22122
|
const ribbonGradient = context.createLinearGradient(firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y);
|
|
22112
22123
|
ribbonGradient.addColorStop(0, `${primaryColor}f2`);
|
|
22113
22124
|
ribbonGradient.addColorStop(0.5, `${secondaryColor}e6`);
|
|
22114
22125
|
ribbonGradient.addColorStop(1, `${tertiaryColor}f2`);
|
|
22126
|
+
// Approximate the blurred shadow stroke with a wider semi-transparent stroke instead of
|
|
22127
|
+
// context.filter blur, which triggers a costly software rasterization pass every frame.
|
|
22115
22128
|
context.save();
|
|
22116
22129
|
context.beginPath();
|
|
22117
22130
|
tracePolyline(context, points);
|
|
22118
|
-
context.strokeStyle = `${shadowColor}
|
|
22119
|
-
context.lineWidth = strokeWidth *
|
|
22131
|
+
context.strokeStyle = `${shadowColor}48`;
|
|
22132
|
+
context.lineWidth = strokeWidth * 4.5;
|
|
22120
22133
|
context.lineJoin = 'round';
|
|
22121
22134
|
context.lineCap = 'round';
|
|
22122
|
-
context.filter = `blur(${size * 0.022}px)`;
|
|
22123
22135
|
context.stroke();
|
|
22124
22136
|
context.restore();
|
|
22125
22137
|
context.beginPath();
|
|
@@ -22581,7 +22593,7 @@
|
|
|
22581
22593
|
*
|
|
22582
22594
|
* @private helper of `minecraft2AvatarVisual`
|
|
22583
22595
|
*/
|
|
22584
|
-
const LIGHT_DIRECTION$
|
|
22596
|
+
const LIGHT_DIRECTION$4 = normalizeVector3({
|
|
22585
22597
|
x: 0.4,
|
|
22586
22598
|
y: -0.65,
|
|
22587
22599
|
z: 0.92,
|
|
@@ -22703,11 +22715,23 @@
|
|
|
22703
22715
|
* @private helper of `minecraft2AvatarVisual`
|
|
22704
22716
|
*/
|
|
22705
22717
|
function drawMinecraftShadow(context, size, palette, interaction, timeMs) {
|
|
22718
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.03;
|
|
22719
|
+
const cy = size * 0.85 + Math.sin(timeMs / 880) * size * 0.01;
|
|
22720
|
+
const rx = size * (0.16 + interaction.intensity * 0.015);
|
|
22721
|
+
const ry = size * 0.055;
|
|
22722
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
22706
22723
|
context.save();
|
|
22707
|
-
context.
|
|
22708
|
-
context.
|
|
22724
|
+
context.translate(cx, cy);
|
|
22725
|
+
context.scale(1, ry / rx);
|
|
22726
|
+
const blurRadius = rx * 1.4;
|
|
22727
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
22728
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
22729
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
22730
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
22731
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
22732
|
+
context.fillStyle = shadowGradient;
|
|
22709
22733
|
context.beginPath();
|
|
22710
|
-
context.
|
|
22734
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
22711
22735
|
context.fill();
|
|
22712
22736
|
context.restore();
|
|
22713
22737
|
}
|
|
@@ -22793,7 +22817,7 @@
|
|
|
22793
22817
|
corners: projectedCorners,
|
|
22794
22818
|
texture: faceDefinition.texture,
|
|
22795
22819
|
averageDepth: transformedCorners.reduce((depthSum, corner) => depthSum + corner.z, 0) / transformedCorners.length,
|
|
22796
|
-
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$
|
|
22820
|
+
lightIntensity: clampNumber$1(dotProduct3D(faceNormal, LIGHT_DIRECTION$4), -1, 1),
|
|
22797
22821
|
outlineColor: cuboid.outlineColor,
|
|
22798
22822
|
};
|
|
22799
22823
|
});
|
|
@@ -22931,13 +22955,27 @@
|
|
|
22931
22955
|
spotlight.addColorStop(1, `${palette.highlight}00`);
|
|
22932
22956
|
context.fillStyle = spotlight;
|
|
22933
22957
|
context.fillRect(0, 0, size, size);
|
|
22934
|
-
|
|
22935
|
-
|
|
22936
|
-
|
|
22937
|
-
|
|
22938
|
-
|
|
22939
|
-
|
|
22940
|
-
|
|
22958
|
+
{
|
|
22959
|
+
// Radial gradient approximates the blurry ellipse shadow without context.filter blur.
|
|
22960
|
+
const cx = size * 0.5;
|
|
22961
|
+
const cy = size * 0.86;
|
|
22962
|
+
const rx = size * 0.2;
|
|
22963
|
+
const ry = size * 0.06;
|
|
22964
|
+
const blurRadius = rx * 1.4;
|
|
22965
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
22966
|
+
shadowGradient.addColorStop(0, 'rgba(0,0,0,0.28)');
|
|
22967
|
+
shadowGradient.addColorStop(0.45, 'rgba(0,0,0,0.14)');
|
|
22968
|
+
shadowGradient.addColorStop(0.8, 'rgba(0,0,0,0.05)');
|
|
22969
|
+
shadowGradient.addColorStop(1, 'rgba(0,0,0,0)');
|
|
22970
|
+
context.save();
|
|
22971
|
+
context.translate(cx, cy);
|
|
22972
|
+
context.scale(1, ry / rx);
|
|
22973
|
+
context.fillStyle = shadowGradient;
|
|
22974
|
+
context.beginPath();
|
|
22975
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
22976
|
+
context.fill();
|
|
22977
|
+
context.restore();
|
|
22978
|
+
}
|
|
22941
22979
|
drawVoxelCuboid(context, {
|
|
22942
22980
|
x: bodyX,
|
|
22943
22981
|
y: bodyY,
|
|
@@ -23466,7 +23504,7 @@
|
|
|
23466
23504
|
*
|
|
23467
23505
|
* @private helper of `octopus3AvatarVisual`
|
|
23468
23506
|
*/
|
|
23469
|
-
function formatAlphaHex$
|
|
23507
|
+
function formatAlphaHex$2(opacity) {
|
|
23470
23508
|
return Math.round(Math.min(1, Math.max(0, opacity)) * 255)
|
|
23471
23509
|
.toString(16)
|
|
23472
23510
|
.padStart(2, '0');
|
|
@@ -23845,7 +23883,7 @@
|
|
|
23845
23883
|
context.beginPath();
|
|
23846
23884
|
context.moveTo(-radiusX * 0.74, radiusY * 0.2);
|
|
23847
23885
|
context.quadraticCurveTo(0, radiusY * 0.38, radiusX * 0.74, radiusY * 0.2);
|
|
23848
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex$
|
|
23886
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$2(eyeStyle.lowerLidOpacity)}`;
|
|
23849
23887
|
context.lineWidth = radiusX * 0.08;
|
|
23850
23888
|
context.lineCap = 'round';
|
|
23851
23889
|
context.stroke();
|
|
@@ -23926,7 +23964,7 @@
|
|
|
23926
23964
|
context.beginPath();
|
|
23927
23965
|
context.moveTo(-projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
23928
23966
|
context.quadraticCurveTo(0, projectedRadiusY * 0.38, projectedRadiusX * 0.74, projectedRadiusY * 0.2);
|
|
23929
|
-
context.strokeStyle = `${palette.highlight}${formatAlphaHex(eyeStyle.lowerLidOpacity)}`;
|
|
23967
|
+
context.strokeStyle = `${palette.highlight}${formatAlphaHex$1(eyeStyle.lowerLidOpacity)}`;
|
|
23930
23968
|
context.lineWidth = projectedRadiusX * 0.08;
|
|
23931
23969
|
context.lineCap = 'round';
|
|
23932
23970
|
context.stroke();
|
|
@@ -23972,7 +24010,7 @@
|
|
|
23972
24010
|
*
|
|
23973
24011
|
* @private helper of the 3D octopus avatar visuals
|
|
23974
24012
|
*/
|
|
23975
|
-
function formatAlphaHex(opacity) {
|
|
24013
|
+
function formatAlphaHex$1(opacity) {
|
|
23976
24014
|
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
23977
24015
|
.toString(16)
|
|
23978
24016
|
.padStart(2, '0');
|
|
@@ -23984,11 +24022,40 @@
|
|
|
23984
24022
|
*
|
|
23985
24023
|
* @private helper of `octopus3dAvatarVisual`
|
|
23986
24024
|
*/
|
|
23987
|
-
const LIGHT_DIRECTION$
|
|
24025
|
+
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
23988
24026
|
x: 0.48,
|
|
23989
24027
|
y: -0.62,
|
|
23990
24028
|
z: 0.94,
|
|
23991
24029
|
});
|
|
24030
|
+
/**
|
|
24031
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
24032
|
+
*
|
|
24033
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
24034
|
+
*/
|
|
24035
|
+
const octopus3dStableStateCache = new WeakMap();
|
|
24036
|
+
/**
|
|
24037
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
24038
|
+
*
|
|
24039
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
24040
|
+
*/
|
|
24041
|
+
function getOctopus3dStableState(createRandom) {
|
|
24042
|
+
const cached = octopus3dStableStateCache.get(createRandom);
|
|
24043
|
+
if (cached !== undefined) {
|
|
24044
|
+
return cached;
|
|
24045
|
+
}
|
|
24046
|
+
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
24047
|
+
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
24048
|
+
const leftEyePhaseOffset = eyeRandom() * 0.6;
|
|
24049
|
+
const rightEyePhaseOffset = eyeRandom() * 0.6;
|
|
24050
|
+
const state = {
|
|
24051
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
24052
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
24053
|
+
leftEyePhaseOffset,
|
|
24054
|
+
rightEyePhaseOffset,
|
|
24055
|
+
};
|
|
24056
|
+
octopus3dStableStateCache.set(createRandom, state);
|
|
24057
|
+
return state;
|
|
24058
|
+
}
|
|
23992
24059
|
/**
|
|
23993
24060
|
* Proper 3D Octopus visual built from projected organic meshes and tentacles.
|
|
23994
24061
|
*
|
|
@@ -24001,10 +24068,7 @@
|
|
|
24001
24068
|
isAnimated: true,
|
|
24002
24069
|
supportsPointerTracking: true,
|
|
24003
24070
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
24004
|
-
const morphologyProfile =
|
|
24005
|
-
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
24006
|
-
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
24007
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
24071
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
|
|
24008
24072
|
const sceneCenterX = size * 0.5;
|
|
24009
24073
|
const sceneCenterY = size * 0.56;
|
|
24010
24074
|
const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
|
|
@@ -24101,12 +24165,12 @@
|
|
|
24101
24165
|
x: -faceEyeSpacing,
|
|
24102
24166
|
y: faceEyeYOffset,
|
|
24103
24167
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
|
|
24104
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
24168
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
24105
24169
|
drawProjectedOrganicEye(context, {
|
|
24106
24170
|
x: faceEyeSpacing,
|
|
24107
24171
|
y: faceEyeYOffset,
|
|
24108
24172
|
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
|
|
24109
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 +
|
|
24173
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
24110
24174
|
drawProjectedOrganicMouth(context, [
|
|
24111
24175
|
{
|
|
24112
24176
|
x: -mouthHalfWidth,
|
|
@@ -24150,14 +24214,28 @@
|
|
|
24150
24214
|
/**
|
|
24151
24215
|
* Draws the soft ground shadow below the octopus.
|
|
24152
24216
|
*
|
|
24217
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
24218
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
24219
|
+
*
|
|
24153
24220
|
* @private helper of `octopus3dAvatarVisual`
|
|
24154
24221
|
*/
|
|
24155
24222
|
function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
|
|
24223
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.04;
|
|
24224
|
+
const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
|
|
24225
|
+
const rx = size * (0.18 + interaction.intensity * 0.02);
|
|
24226
|
+
const ry = size * 0.06;
|
|
24156
24227
|
context.save();
|
|
24157
|
-
context.
|
|
24158
|
-
context.
|
|
24228
|
+
context.translate(cx, cy);
|
|
24229
|
+
context.scale(1, ry / rx);
|
|
24230
|
+
const blurRadius = rx * 1.4;
|
|
24231
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
24232
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
24233
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
24234
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
24235
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
24236
|
+
context.fillStyle = shadowGradient;
|
|
24159
24237
|
context.beginPath();
|
|
24160
|
-
context.
|
|
24238
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
24161
24239
|
context.fill();
|
|
24162
24240
|
context.restore();
|
|
24163
24241
|
}
|
|
@@ -24194,7 +24272,7 @@
|
|
|
24194
24272
|
corners: projectedCorners,
|
|
24195
24273
|
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
24196
24274
|
transformedCorners.length,
|
|
24197
|
-
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$
|
|
24275
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
24198
24276
|
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
24199
24277
|
outlineColor,
|
|
24200
24278
|
});
|
|
@@ -24383,11 +24461,40 @@
|
|
|
24383
24461
|
*
|
|
24384
24462
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24385
24463
|
*/
|
|
24386
|
-
const LIGHT_DIRECTION = normalizeVector3({
|
|
24464
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
24387
24465
|
x: 0.38,
|
|
24388
24466
|
y: -0.6,
|
|
24389
24467
|
z: 0.98,
|
|
24390
24468
|
});
|
|
24469
|
+
/**
|
|
24470
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
24471
|
+
*
|
|
24472
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
24473
|
+
*/
|
|
24474
|
+
const octopus3d2StableStateCache = new WeakMap();
|
|
24475
|
+
/**
|
|
24476
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
24477
|
+
*
|
|
24478
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
24479
|
+
*/
|
|
24480
|
+
function getOctopus3d2StableState(createRandom) {
|
|
24481
|
+
const cached = octopus3d2StableStateCache.get(createRandom);
|
|
24482
|
+
if (cached !== undefined) {
|
|
24483
|
+
return cached;
|
|
24484
|
+
}
|
|
24485
|
+
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
24486
|
+
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
24487
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
24488
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
24489
|
+
const state = {
|
|
24490
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
24491
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
24492
|
+
leftEyePhaseOffset,
|
|
24493
|
+
rightEyePhaseOffset,
|
|
24494
|
+
};
|
|
24495
|
+
octopus3d2StableStateCache.set(createRandom, state);
|
|
24496
|
+
return state;
|
|
24497
|
+
}
|
|
24391
24498
|
/**
|
|
24392
24499
|
* Octopus 3D 2 avatar visual.
|
|
24393
24500
|
*
|
|
@@ -24400,10 +24507,7 @@
|
|
|
24400
24507
|
isAnimated: true,
|
|
24401
24508
|
supportsPointerTracking: true,
|
|
24402
24509
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
24403
|
-
const morphologyProfile =
|
|
24404
|
-
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
24405
|
-
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
24406
|
-
const animationPhase = animationRandom() * Math.PI * 2;
|
|
24510
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
|
|
24407
24511
|
const sceneCenterX = size * 0.5;
|
|
24408
24512
|
const sceneCenterY = size * 0.575;
|
|
24409
24513
|
const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
|
|
@@ -24456,8 +24560,8 @@
|
|
|
24456
24560
|
const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
|
|
24457
24561
|
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
24458
24562
|
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
24459
|
-
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase +
|
|
24460
|
-
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 +
|
|
24563
|
+
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
24564
|
+
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
24461
24565
|
drawProjectedOrganicMouth(context, [
|
|
24462
24566
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
24463
24567
|
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
@@ -24486,54 +24590,118 @@
|
|
|
24486
24590
|
/**
|
|
24487
24591
|
* Draws the soft floor shadow that anchors the single mesh in the frame.
|
|
24488
24592
|
*
|
|
24593
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
24594
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
24595
|
+
*
|
|
24489
24596
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24490
24597
|
*/
|
|
24491
24598
|
function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
24599
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
24600
|
+
const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
|
|
24601
|
+
const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
|
|
24602
|
+
const ry = size * 0.062;
|
|
24492
24603
|
context.save();
|
|
24493
|
-
context.
|
|
24494
|
-
context.
|
|
24604
|
+
context.translate(cx, cy);
|
|
24605
|
+
context.scale(1, ry / rx);
|
|
24606
|
+
const blurRadius = rx * 1.4;
|
|
24607
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
24608
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
24609
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
24610
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
24611
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
24612
|
+
context.fillStyle = shadowGradient;
|
|
24495
24613
|
context.beginPath();
|
|
24496
|
-
context.
|
|
24614
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
24497
24615
|
context.fill();
|
|
24498
24616
|
context.restore();
|
|
24499
24617
|
}
|
|
24618
|
+
/**
|
|
24619
|
+
* Number of latitude segments used by the single blobby octopus mesh.
|
|
24620
|
+
*
|
|
24621
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
24622
|
+
*/
|
|
24623
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
24624
|
+
/**
|
|
24625
|
+
* Number of longitude segments used by the single blobby octopus mesh.
|
|
24626
|
+
*
|
|
24627
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
24628
|
+
*/
|
|
24629
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
24500
24630
|
/**
|
|
24501
24631
|
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
24502
24632
|
*
|
|
24633
|
+
* Within a single frame, mesh corner samples and longitude-only lobe-wave values are
|
|
24634
|
+
* quantized to the patch grid and computed once each rather than re-evaluated for every
|
|
24635
|
+
* patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
|
|
24636
|
+
* `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
|
|
24637
|
+
* corners are shared between neighboring patches.
|
|
24638
|
+
*
|
|
24503
24639
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24504
24640
|
*/
|
|
24505
24641
|
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
24506
24642
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
24507
|
-
const latitudePatchCount =
|
|
24508
|
-
const longitudePatchCount =
|
|
24643
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
24644
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
24509
24645
|
const surfacePatches = [];
|
|
24646
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
24647
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
24648
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
24649
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
24650
|
+
}
|
|
24651
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
24652
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
24653
|
+
}
|
|
24654
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
24655
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
24656
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
|
|
24657
|
+
}
|
|
24658
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
24659
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
24660
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
24661
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
24662
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
24663
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
24664
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
24665
|
+
const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
24666
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
24667
|
+
}
|
|
24668
|
+
}
|
|
24510
24669
|
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
24511
|
-
const startLatitude =
|
|
24512
|
-
const endLatitude =
|
|
24670
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
24671
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
24513
24672
|
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
24514
24673
|
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
24674
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
24675
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
24515
24676
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
24516
|
-
const startLongitude =
|
|
24517
|
-
const endLongitude =
|
|
24677
|
+
const startLongitude = longitudeBoundaries[longitudeIndex];
|
|
24678
|
+
const endLongitude = longitudeBoundaries[longitudeIndex + 1];
|
|
24518
24679
|
const centerLongitude = (startLongitude + endLongitude) / 2;
|
|
24519
|
-
const
|
|
24520
|
-
|
|
24521
|
-
|
|
24522
|
-
|
|
24523
|
-
|
|
24680
|
+
const transformedCorners = [
|
|
24681
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
24682
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
24683
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
24684
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
24524
24685
|
];
|
|
24525
|
-
const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
|
|
24526
24686
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
24527
24687
|
if (surfaceNormal.z <= 0.01) {
|
|
24528
24688
|
continue;
|
|
24529
24689
|
}
|
|
24530
|
-
const projectedCorners =
|
|
24690
|
+
const projectedCorners = [
|
|
24691
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
24692
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
24693
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
24694
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
24695
|
+
];
|
|
24531
24696
|
surfacePatches.push({
|
|
24532
24697
|
corners: projectedCorners,
|
|
24533
|
-
averageDepth: transformedCorners.
|
|
24534
|
-
transformedCorners.
|
|
24535
|
-
|
|
24536
|
-
|
|
24698
|
+
averageDepth: (transformedCorners[0].z +
|
|
24699
|
+
transformedCorners[1].z +
|
|
24700
|
+
transformedCorners[2].z +
|
|
24701
|
+
transformedCorners[3].z) /
|
|
24702
|
+
4,
|
|
24703
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
24704
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
24537
24705
|
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
24538
24706
|
});
|
|
24539
24707
|
}
|
|
@@ -24549,12 +24717,21 @@
|
|
|
24549
24717
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24550
24718
|
*/
|
|
24551
24719
|
function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
|
|
24720
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
24721
|
+
return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
|
|
24722
|
+
}
|
|
24723
|
+
/**
|
|
24724
|
+
* Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
|
|
24725
|
+
* to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
|
|
24726
|
+
*
|
|
24727
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
24728
|
+
*/
|
|
24729
|
+
function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
|
|
24552
24730
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
24553
24731
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
24554
24732
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
24555
24733
|
const upperBlend = Math.pow(1 - verticalProgress, 1.2);
|
|
24556
24734
|
const lowerBlend = Math.pow(verticalProgress, 1.42);
|
|
24557
|
-
const lowerLobeWave = resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs);
|
|
24558
24735
|
const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
|
|
24559
24736
|
const horizontalScale = 1.02 +
|
|
24560
24737
|
skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
|
|
@@ -24592,7 +24769,7 @@
|
|
|
24592
24769
|
*
|
|
24593
24770
|
* @private helper of `octopus3d2AvatarVisual`
|
|
24594
24771
|
*/
|
|
24595
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
24772
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
24596
24773
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
24597
24774
|
if (tonalProgress < 0.16) {
|
|
24598
24775
|
return palette.highlight;
|
|
@@ -24632,6 +24809,1384 @@
|
|
|
24632
24809
|
context.restore();
|
|
24633
24810
|
}
|
|
24634
24811
|
|
|
24812
|
+
/* eslint-disable no-magic-numbers */
|
|
24813
|
+
/**
|
|
24814
|
+
* Light direction used by the continuous octopus mesh shading.
|
|
24815
|
+
*
|
|
24816
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
24817
|
+
*/
|
|
24818
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
24819
|
+
x: 0.34,
|
|
24820
|
+
y: -0.62,
|
|
24821
|
+
z: 1,
|
|
24822
|
+
});
|
|
24823
|
+
/**
|
|
24824
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
24825
|
+
*
|
|
24826
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
24827
|
+
*/
|
|
24828
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
24829
|
+
/**
|
|
24830
|
+
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
24831
|
+
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
24832
|
+
* React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
|
|
24833
|
+
*
|
|
24834
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
24835
|
+
*/
|
|
24836
|
+
const stableStateCache = new WeakMap();
|
|
24837
|
+
/**
|
|
24838
|
+
* Returns the stable per-avatar state, computing it on first access and returning the cached
|
|
24839
|
+
* result on every subsequent call within the same `<Avatar/>` mount.
|
|
24840
|
+
*
|
|
24841
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
24842
|
+
*/
|
|
24843
|
+
function getOctopus3d3StableState(createRandom) {
|
|
24844
|
+
const cached = stableStateCache.get(createRandom);
|
|
24845
|
+
if (cached !== undefined) {
|
|
24846
|
+
return cached;
|
|
24847
|
+
}
|
|
24848
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
24849
|
+
const animationRandom = createRandom('octopus3d3-animation-profile');
|
|
24850
|
+
const eyeRandom = createRandom('octopus3d3-eye-profile');
|
|
24851
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
24852
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
24853
|
+
const state = {
|
|
24854
|
+
morphologyProfile,
|
|
24855
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
24856
|
+
leftEyePhaseOffset,
|
|
24857
|
+
rightEyePhaseOffset,
|
|
24858
|
+
tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
24859
|
+
};
|
|
24860
|
+
stableStateCache.set(createRandom, state);
|
|
24861
|
+
return state;
|
|
24862
|
+
}
|
|
24863
|
+
/**
|
|
24864
|
+
* Octopus 3D 3 avatar visual.
|
|
24865
|
+
*
|
|
24866
|
+
* @private built-in avatar visual
|
|
24867
|
+
*/
|
|
24868
|
+
const octopus3d3AvatarVisual = {
|
|
24869
|
+
id: 'octopus3d3',
|
|
24870
|
+
title: 'Octopus 3D 3',
|
|
24871
|
+
description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
|
|
24872
|
+
isAnimated: true,
|
|
24873
|
+
supportsPointerTracking: true,
|
|
24874
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
24875
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
|
|
24876
|
+
const sceneCenterX = size * 0.5;
|
|
24877
|
+
const sceneCenterY = size * 0.535;
|
|
24878
|
+
const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
|
|
24879
|
+
const meshCenter = {
|
|
24880
|
+
x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
24881
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
24882
|
+
z: interaction.intensity * size * 0.018,
|
|
24883
|
+
};
|
|
24884
|
+
const rotationY = -0.1 +
|
|
24885
|
+
Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
|
|
24886
|
+
interaction.bodyOffsetX * 0.22 +
|
|
24887
|
+
interaction.gazeX * 0.88;
|
|
24888
|
+
const rotationX = -0.07 +
|
|
24889
|
+
Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
|
|
24890
|
+
interaction.bodyOffsetY * 0.08 -
|
|
24891
|
+
interaction.gazeY * 0.38;
|
|
24892
|
+
const surfaceOptions = {
|
|
24893
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
|
|
24894
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
|
|
24895
|
+
radiusZ: size *
|
|
24896
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
24897
|
+
(1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
|
|
24898
|
+
morphologyProfile,
|
|
24899
|
+
timeMs,
|
|
24900
|
+
animationPhase,
|
|
24901
|
+
tentacleProfiles,
|
|
24902
|
+
};
|
|
24903
|
+
const surfacePatches = resolveVisibleContinuousOctopusPatches({
|
|
24904
|
+
...surfaceOptions,
|
|
24905
|
+
center: meshCenter,
|
|
24906
|
+
rotationX,
|
|
24907
|
+
rotationY,
|
|
24908
|
+
sceneCenterX,
|
|
24909
|
+
sceneCenterY,
|
|
24910
|
+
size,
|
|
24911
|
+
palette,
|
|
24912
|
+
});
|
|
24913
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
|
|
24914
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
|
|
24915
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
24916
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
24917
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
24918
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
24919
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
|
|
24920
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
|
|
24921
|
+
drawAvatarFrame(context, size, palette);
|
|
24922
|
+
drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
24923
|
+
drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
24924
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
24925
|
+
drawContinuousSurfacePatch(context, surfacePatch);
|
|
24926
|
+
}
|
|
24927
|
+
drawProjectedSurfaceCurrents({
|
|
24928
|
+
context,
|
|
24929
|
+
surfaceOptions,
|
|
24930
|
+
center: meshCenter,
|
|
24931
|
+
rotationX,
|
|
24932
|
+
rotationY,
|
|
24933
|
+
sceneCenterX,
|
|
24934
|
+
sceneCenterY,
|
|
24935
|
+
size,
|
|
24936
|
+
palette,
|
|
24937
|
+
morphologyProfile,
|
|
24938
|
+
timeMs,
|
|
24939
|
+
animationPhase,
|
|
24940
|
+
});
|
|
24941
|
+
drawProjectedTentacleSuckers({
|
|
24942
|
+
context,
|
|
24943
|
+
surfaceOptions,
|
|
24944
|
+
center: meshCenter,
|
|
24945
|
+
rotationX,
|
|
24946
|
+
rotationY,
|
|
24947
|
+
sceneCenterX,
|
|
24948
|
+
sceneCenterY,
|
|
24949
|
+
size,
|
|
24950
|
+
palette,
|
|
24951
|
+
});
|
|
24952
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
24953
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
24954
|
+
drawProjectedOrganicMouth(context, [
|
|
24955
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
24956
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
24957
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
24958
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
24959
|
+
},
|
|
24960
|
+
};
|
|
24961
|
+
/**
|
|
24962
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
24963
|
+
*
|
|
24964
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
24965
|
+
*/
|
|
24966
|
+
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
24967
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
24968
|
+
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
24969
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
24970
|
+
return {
|
|
24971
|
+
centerLongitude: -Math.PI * 0.86 +
|
|
24972
|
+
progress * Math.PI * 1.72 +
|
|
24973
|
+
(tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
|
|
24974
|
+
widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
|
|
24975
|
+
lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
|
|
24976
|
+
swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
|
|
24977
|
+
depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
|
|
24978
|
+
phase: tentacleRandom() * Math.PI * 2,
|
|
24979
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
24980
|
+
};
|
|
24981
|
+
});
|
|
24982
|
+
}
|
|
24983
|
+
/**
|
|
24984
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
24985
|
+
*
|
|
24986
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
24987
|
+
*/
|
|
24988
|
+
function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
24989
|
+
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));
|
|
24990
|
+
glowGradient.addColorStop(0, `${palette.highlight}66`);
|
|
24991
|
+
glowGradient.addColorStop(0.34, `${palette.accent}2e`);
|
|
24992
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
24993
|
+
context.fillStyle = glowGradient;
|
|
24994
|
+
context.fillRect(0, 0, size, size);
|
|
24995
|
+
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);
|
|
24996
|
+
lowerGradient.addColorStop(0, `${palette.secondary}25`);
|
|
24997
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
24998
|
+
context.fillStyle = lowerGradient;
|
|
24999
|
+
context.fillRect(0, 0, size, size);
|
|
25000
|
+
}
|
|
25001
|
+
/**
|
|
25002
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
25003
|
+
*
|
|
25004
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
25005
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
25006
|
+
*
|
|
25007
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25008
|
+
*/
|
|
25009
|
+
function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
25010
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
25011
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
25012
|
+
const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
|
|
25013
|
+
const ry = size * 0.06;
|
|
25014
|
+
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
25015
|
+
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
25016
|
+
context.save();
|
|
25017
|
+
context.translate(cx, cy);
|
|
25018
|
+
context.scale(1, ry / rx);
|
|
25019
|
+
const blurRadius = rx * 1.4;
|
|
25020
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
25021
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
25022
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
25023
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
25024
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
25025
|
+
context.fillStyle = shadowGradient;
|
|
25026
|
+
context.beginPath();
|
|
25027
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
25028
|
+
context.fill();
|
|
25029
|
+
context.restore();
|
|
25030
|
+
}
|
|
25031
|
+
/**
|
|
25032
|
+
* Number of latitude segments used by the continuous Octopus 3D 3 mesh.
|
|
25033
|
+
*
|
|
25034
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25035
|
+
*/
|
|
25036
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
25037
|
+
/**
|
|
25038
|
+
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
25039
|
+
*
|
|
25040
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25041
|
+
*/
|
|
25042
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
25043
|
+
/**
|
|
25044
|
+
* Resolves visible projected patches for the continuous octopus mesh.
|
|
25045
|
+
*
|
|
25046
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
25047
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
25048
|
+
* than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
|
|
25049
|
+
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
25050
|
+
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
25051
|
+
*
|
|
25052
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25053
|
+
*/
|
|
25054
|
+
function resolveVisibleContinuousOctopusPatches(options) {
|
|
25055
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
25056
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
25057
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
25058
|
+
const surfacePatches = [];
|
|
25059
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
25060
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
25061
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
25062
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
25063
|
+
}
|
|
25064
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
25065
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
25066
|
+
}
|
|
25067
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
25068
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
25069
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
25070
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
25071
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
25072
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
25073
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
25074
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
|
|
25075
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
|
|
25076
|
+
}
|
|
25077
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
25078
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
25079
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
|
|
25080
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
|
|
25081
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
25082
|
+
}
|
|
25083
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
25084
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
25085
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
25086
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
25087
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
25088
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
25089
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
25090
|
+
const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
25091
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
25092
|
+
}
|
|
25093
|
+
}
|
|
25094
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
25095
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
25096
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
25097
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
25098
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
25099
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
25100
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
25101
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
25102
|
+
const transformedCorners = [
|
|
25103
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
25104
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
25105
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
25106
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
25107
|
+
];
|
|
25108
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
25109
|
+
if (surfaceNormal.z <= 0.008) {
|
|
25110
|
+
continue;
|
|
25111
|
+
}
|
|
25112
|
+
const projectedCorners = [
|
|
25113
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
25114
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
25115
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
25116
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
25117
|
+
];
|
|
25118
|
+
surfacePatches.push({
|
|
25119
|
+
corners: projectedCorners,
|
|
25120
|
+
averageDepth: (transformedCorners[0].z +
|
|
25121
|
+
transformedCorners[1].z +
|
|
25122
|
+
transformedCorners[2].z +
|
|
25123
|
+
transformedCorners[3].z) /
|
|
25124
|
+
4,
|
|
25125
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1),
|
|
25126
|
+
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
25127
|
+
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
25128
|
+
});
|
|
25129
|
+
}
|
|
25130
|
+
}
|
|
25131
|
+
return surfacePatches;
|
|
25132
|
+
}
|
|
25133
|
+
/**
|
|
25134
|
+
* Samples one point on the continuous Octopus 3D 3 surface.
|
|
25135
|
+
*
|
|
25136
|
+
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
25137
|
+
* tentacled while still being rendered as one connected blobby mesh.
|
|
25138
|
+
*
|
|
25139
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25140
|
+
*/
|
|
25141
|
+
function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
|
|
25142
|
+
return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
|
|
25143
|
+
}
|
|
25144
|
+
/**
|
|
25145
|
+
* Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
|
|
25146
|
+
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
25147
|
+
*
|
|
25148
|
+
* The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
|
|
25149
|
+
* corner grid, so the same longitude is reused across every latitude row and each
|
|
25150
|
+
* tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
|
|
25151
|
+
*
|
|
25152
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25153
|
+
*/
|
|
25154
|
+
function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
25155
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
25156
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
25157
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
25158
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
25159
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
25160
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
25161
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
25162
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
25163
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
25164
|
+
animationPhase * 0.6 +
|
|
25165
|
+
timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
|
|
25166
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
|
|
25167
|
+
(0.3 + lowerBlend * 0.7);
|
|
25168
|
+
const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
|
|
25169
|
+
lowerBlend *
|
|
25170
|
+
tentacleInfluence.core *
|
|
25171
|
+
tentacleInfluence.swayScale;
|
|
25172
|
+
const horizontalScale = 1.04 +
|
|
25173
|
+
mantleRipple +
|
|
25174
|
+
lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
|
|
25175
|
+
lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
|
|
25176
|
+
upperBlend * 0.08;
|
|
25177
|
+
const depthScale = 1.06 +
|
|
25178
|
+
upperBlend * 0.16 +
|
|
25179
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
|
|
25180
|
+
lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
|
|
25181
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
25182
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
|
|
25183
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
25184
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
|
|
25185
|
+
const lowerDrop = lowerBlend *
|
|
25186
|
+
radiusY *
|
|
25187
|
+
(0.18 +
|
|
25188
|
+
tentacleInfluence.core *
|
|
25189
|
+
(0.38 +
|
|
25190
|
+
tentacleInfluence.lengthScale * 0.22 +
|
|
25191
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
|
|
25192
|
+
return {
|
|
25193
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
|
|
25194
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
|
|
25195
|
+
upperBlend * radiusY * 0.1 +
|
|
25196
|
+
lowerDrop +
|
|
25197
|
+
Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
|
|
25198
|
+
Math.cos(timeMs / 880 + tentacleInfluence.phase) *
|
|
25199
|
+
lowerBlend *
|
|
25200
|
+
tipBlend *
|
|
25201
|
+
tentacleInfluence.core *
|
|
25202
|
+
radiusY *
|
|
25203
|
+
0.034,
|
|
25204
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
25205
|
+
Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
|
|
25206
|
+
lowerBlend *
|
|
25207
|
+
tentacleInfluence.core *
|
|
25208
|
+
radiusZ *
|
|
25209
|
+
0.04,
|
|
25210
|
+
};
|
|
25211
|
+
}
|
|
25212
|
+
/**
|
|
25213
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
25214
|
+
*
|
|
25215
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25216
|
+
*/
|
|
25217
|
+
function resolveContinuousTentacleInfluence(options, longitude) {
|
|
25218
|
+
let totalWeight = 0;
|
|
25219
|
+
let weightedSin = 0;
|
|
25220
|
+
let weightedCos = 0;
|
|
25221
|
+
let weightedWidthScale = 0;
|
|
25222
|
+
let weightedLengthScale = 0;
|
|
25223
|
+
let weightedSwayScale = 0;
|
|
25224
|
+
let weightedDepthScale = 0;
|
|
25225
|
+
let weightedPhase = 0;
|
|
25226
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
25227
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
25228
|
+
const width = 0.2 * tentacleProfile.widthScale;
|
|
25229
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
25230
|
+
totalWeight += weight;
|
|
25231
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
25232
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
25233
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
25234
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
25235
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
25236
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
25237
|
+
weightedPhase += tentacleProfile.phase * weight;
|
|
25238
|
+
}
|
|
25239
|
+
if (totalWeight < 0.0001) {
|
|
25240
|
+
return {
|
|
25241
|
+
core: 0,
|
|
25242
|
+
centerLongitude: longitude,
|
|
25243
|
+
widthScale: 1,
|
|
25244
|
+
lengthScale: 1,
|
|
25245
|
+
swayScale: 1,
|
|
25246
|
+
depthScale: 1,
|
|
25247
|
+
phase: 0,
|
|
25248
|
+
};
|
|
25249
|
+
}
|
|
25250
|
+
return {
|
|
25251
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
25252
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
25253
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
25254
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
25255
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
25256
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
25257
|
+
phase: weightedPhase / totalWeight,
|
|
25258
|
+
};
|
|
25259
|
+
}
|
|
25260
|
+
/**
|
|
25261
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
25262
|
+
*
|
|
25263
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25264
|
+
*/
|
|
25265
|
+
function resolveContinuousLobeWave(options, longitude) {
|
|
25266
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
25267
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
25268
|
+
animationPhase +
|
|
25269
|
+
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
25270
|
+
1) /
|
|
25271
|
+
2);
|
|
25272
|
+
}
|
|
25273
|
+
/**
|
|
25274
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
25275
|
+
*
|
|
25276
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25277
|
+
*/
|
|
25278
|
+
function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
25279
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
25280
|
+
if (tonalProgress < 0.14) {
|
|
25281
|
+
return palette.highlight;
|
|
25282
|
+
}
|
|
25283
|
+
if (tonalProgress < 0.32) {
|
|
25284
|
+
return palette.secondary;
|
|
25285
|
+
}
|
|
25286
|
+
if (tonalProgress < 0.72) {
|
|
25287
|
+
return forwardness > 0.55 ? palette.secondary : palette.primary;
|
|
25288
|
+
}
|
|
25289
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
25290
|
+
}
|
|
25291
|
+
/**
|
|
25292
|
+
* Draws one projected mesh patch with soft shading and a subtle edge.
|
|
25293
|
+
*
|
|
25294
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25295
|
+
*/
|
|
25296
|
+
function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
25297
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
25298
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
25299
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
|
|
25300
|
+
}
|
|
25301
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
25302
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
25303
|
+
}
|
|
25304
|
+
context.save();
|
|
25305
|
+
context.beginPath();
|
|
25306
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
25307
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
25308
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
25309
|
+
}
|
|
25310
|
+
context.closePath();
|
|
25311
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
25312
|
+
context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
|
|
25313
|
+
context.lineJoin = 'round';
|
|
25314
|
+
context.stroke();
|
|
25315
|
+
context.restore();
|
|
25316
|
+
}
|
|
25317
|
+
/**
|
|
25318
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
25319
|
+
*
|
|
25320
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25321
|
+
*/
|
|
25322
|
+
function drawProjectedSurfaceCurrents(options) {
|
|
25323
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
25324
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
25325
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
25326
|
+
context.save();
|
|
25327
|
+
context.lineCap = 'round';
|
|
25328
|
+
context.lineJoin = 'round';
|
|
25329
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
25330
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
25331
|
+
const projectedPoints = [];
|
|
25332
|
+
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
|
|
25333
|
+
const progress = sampleIndex / 7;
|
|
25334
|
+
const latitude = -0.46 + progress * 0.74;
|
|
25335
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
|
|
25336
|
+
const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
25337
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
25338
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
25339
|
+
}
|
|
25340
|
+
}
|
|
25341
|
+
if (projectedPoints.length < 3) {
|
|
25342
|
+
continue;
|
|
25343
|
+
}
|
|
25344
|
+
context.beginPath();
|
|
25345
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
25346
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
25347
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
25348
|
+
}
|
|
25349
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
25350
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
25351
|
+
context.stroke();
|
|
25352
|
+
}
|
|
25353
|
+
context.restore();
|
|
25354
|
+
}
|
|
25355
|
+
/**
|
|
25356
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
25357
|
+
*
|
|
25358
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25359
|
+
*/
|
|
25360
|
+
function drawProjectedTentacleSuckers(options) {
|
|
25361
|
+
const { surfaceOptions, size } = options;
|
|
25362
|
+
const { timeMs } = surfaceOptions;
|
|
25363
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
25364
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
|
|
25365
|
+
continue;
|
|
25366
|
+
}
|
|
25367
|
+
for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
|
|
25368
|
+
const latitude = 0.52 + suckerIndex * 0.14;
|
|
25369
|
+
const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
|
|
25370
|
+
const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
|
|
25371
|
+
drawProjectedSurfaceSpot({
|
|
25372
|
+
...options,
|
|
25373
|
+
latitude,
|
|
25374
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
25375
|
+
radiusScale: size * (0.0065 - suckerIndex * 0.0007),
|
|
25376
|
+
});
|
|
25377
|
+
}
|
|
25378
|
+
}
|
|
25379
|
+
}
|
|
25380
|
+
/**
|
|
25381
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
25382
|
+
*
|
|
25383
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25384
|
+
*/
|
|
25385
|
+
function drawProjectedSurfaceSpot(options) {
|
|
25386
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
25387
|
+
const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
|
|
25388
|
+
const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
25389
|
+
const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
25390
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
25391
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
25392
|
+
return;
|
|
25393
|
+
}
|
|
25394
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
25395
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
25396
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
25397
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
25398
|
+
radiusScale *
|
|
25399
|
+
0.74, size * 0.003, size * 0.018);
|
|
25400
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
25401
|
+
radiusScale *
|
|
25402
|
+
0.52, size * 0.0024, size * 0.014);
|
|
25403
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
25404
|
+
context.save();
|
|
25405
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
25406
|
+
context.rotate(rotation);
|
|
25407
|
+
context.beginPath();
|
|
25408
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
25409
|
+
context.fillStyle = `${palette.highlight}73`;
|
|
25410
|
+
context.fill();
|
|
25411
|
+
context.strokeStyle = `${palette.highlight}99`;
|
|
25412
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
25413
|
+
context.stroke();
|
|
25414
|
+
context.restore();
|
|
25415
|
+
}
|
|
25416
|
+
/**
|
|
25417
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
25418
|
+
*
|
|
25419
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25420
|
+
*/
|
|
25421
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
25422
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
25423
|
+
}
|
|
25424
|
+
/**
|
|
25425
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
25426
|
+
*
|
|
25427
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
25428
|
+
*/
|
|
25429
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
25430
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
25431
|
+
return progress * progress * (3 - 2 * progress);
|
|
25432
|
+
}
|
|
25433
|
+
|
|
25434
|
+
/* eslint-disable no-magic-numbers */
|
|
25435
|
+
/**
|
|
25436
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
25437
|
+
*
|
|
25438
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25439
|
+
*/
|
|
25440
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
25441
|
+
x: 0.32,
|
|
25442
|
+
y: -0.66,
|
|
25443
|
+
z: 1,
|
|
25444
|
+
});
|
|
25445
|
+
/**
|
|
25446
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
25447
|
+
*
|
|
25448
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25449
|
+
*/
|
|
25450
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
25451
|
+
x: -0.7,
|
|
25452
|
+
y: -0.18,
|
|
25453
|
+
z: 0.55,
|
|
25454
|
+
});
|
|
25455
|
+
/**
|
|
25456
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
25457
|
+
*
|
|
25458
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25459
|
+
*/
|
|
25460
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
25461
|
+
/**
|
|
25462
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
25463
|
+
*
|
|
25464
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25465
|
+
*/
|
|
25466
|
+
const SKIN_SPOT_COUNT = 14;
|
|
25467
|
+
/**
|
|
25468
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
25469
|
+
*
|
|
25470
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25471
|
+
*/
|
|
25472
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
25473
|
+
/**
|
|
25474
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
25475
|
+
*
|
|
25476
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25477
|
+
*/
|
|
25478
|
+
function getOctopus3d4StableState(createRandom) {
|
|
25479
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
25480
|
+
if (cached !== undefined) {
|
|
25481
|
+
return cached;
|
|
25482
|
+
}
|
|
25483
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
25484
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
25485
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
25486
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
25487
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
25488
|
+
const state = {
|
|
25489
|
+
morphologyProfile,
|
|
25490
|
+
animationPhase: animationRandom() * Math.PI * 2,
|
|
25491
|
+
leftEyePhaseOffset,
|
|
25492
|
+
rightEyePhaseOffset,
|
|
25493
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
25494
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
25495
|
+
};
|
|
25496
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
25497
|
+
return state;
|
|
25498
|
+
}
|
|
25499
|
+
/**
|
|
25500
|
+
* Octopus 3D 4 avatar visual.
|
|
25501
|
+
*
|
|
25502
|
+
* @private built-in avatar visual
|
|
25503
|
+
*/
|
|
25504
|
+
const octopus3d4AvatarVisual = {
|
|
25505
|
+
id: 'octopus3d4',
|
|
25506
|
+
title: 'Octopus 3D 4',
|
|
25507
|
+
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.',
|
|
25508
|
+
isAnimated: true,
|
|
25509
|
+
supportsPointerTracking: true,
|
|
25510
|
+
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
25511
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots } = getOctopus3d4StableState(createRandom);
|
|
25512
|
+
const sceneCenterX = size * 0.5;
|
|
25513
|
+
const sceneCenterY = size * 0.535;
|
|
25514
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
25515
|
+
const meshCenter = {
|
|
25516
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
25517
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
25518
|
+
z: interaction.intensity * size * 0.02,
|
|
25519
|
+
};
|
|
25520
|
+
const rotationY = -0.08 +
|
|
25521
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
25522
|
+
interaction.bodyOffsetX * 0.24 +
|
|
25523
|
+
interaction.gazeX * 0.98;
|
|
25524
|
+
const rotationX = -0.07 +
|
|
25525
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
25526
|
+
interaction.bodyOffsetY * 0.08 -
|
|
25527
|
+
interaction.gazeY * 0.42;
|
|
25528
|
+
const surfaceOptions = {
|
|
25529
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
25530
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
25531
|
+
radiusZ: size *
|
|
25532
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
25533
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
25534
|
+
morphologyProfile,
|
|
25535
|
+
timeMs,
|
|
25536
|
+
animationPhase,
|
|
25537
|
+
tentacleProfiles,
|
|
25538
|
+
};
|
|
25539
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
25540
|
+
...surfaceOptions,
|
|
25541
|
+
center: meshCenter,
|
|
25542
|
+
rotationX,
|
|
25543
|
+
rotationY,
|
|
25544
|
+
sceneCenterX,
|
|
25545
|
+
sceneCenterY,
|
|
25546
|
+
size,
|
|
25547
|
+
palette,
|
|
25548
|
+
});
|
|
25549
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
25550
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
25551
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
25552
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
25553
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
25554
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
25555
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
25556
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
25557
|
+
drawAvatarFrame(context, size, palette);
|
|
25558
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
25559
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
25560
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
25561
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
25562
|
+
}
|
|
25563
|
+
drawBlobbySkinSpots({
|
|
25564
|
+
context,
|
|
25565
|
+
surfaceOptions,
|
|
25566
|
+
center: meshCenter,
|
|
25567
|
+
rotationX,
|
|
25568
|
+
rotationY,
|
|
25569
|
+
sceneCenterX,
|
|
25570
|
+
sceneCenterY,
|
|
25571
|
+
size,
|
|
25572
|
+
palette,
|
|
25573
|
+
skinSpots,
|
|
25574
|
+
});
|
|
25575
|
+
drawBlobbyContinuousCurrents({
|
|
25576
|
+
context,
|
|
25577
|
+
surfaceOptions,
|
|
25578
|
+
center: meshCenter,
|
|
25579
|
+
rotationX,
|
|
25580
|
+
rotationY,
|
|
25581
|
+
sceneCenterX,
|
|
25582
|
+
sceneCenterY,
|
|
25583
|
+
size,
|
|
25584
|
+
palette,
|
|
25585
|
+
morphologyProfile,
|
|
25586
|
+
timeMs,
|
|
25587
|
+
animationPhase,
|
|
25588
|
+
});
|
|
25589
|
+
drawBlobbyContinuousSuckers({
|
|
25590
|
+
context,
|
|
25591
|
+
surfaceOptions,
|
|
25592
|
+
center: meshCenter,
|
|
25593
|
+
rotationX,
|
|
25594
|
+
rotationY,
|
|
25595
|
+
sceneCenterX,
|
|
25596
|
+
sceneCenterY,
|
|
25597
|
+
size,
|
|
25598
|
+
palette,
|
|
25599
|
+
});
|
|
25600
|
+
drawBlobbyContinuousGloss({
|
|
25601
|
+
context,
|
|
25602
|
+
surfaceOptions,
|
|
25603
|
+
center: meshCenter,
|
|
25604
|
+
rotationX,
|
|
25605
|
+
rotationY,
|
|
25606
|
+
sceneCenterX,
|
|
25607
|
+
sceneCenterY,
|
|
25608
|
+
size,
|
|
25609
|
+
palette,
|
|
25610
|
+
});
|
|
25611
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
25612
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
25613
|
+
drawProjectedOrganicMouth(context, [
|
|
25614
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
25615
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
25616
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
25617
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
25618
|
+
},
|
|
25619
|
+
};
|
|
25620
|
+
/**
|
|
25621
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
25622
|
+
*
|
|
25623
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25624
|
+
*/
|
|
25625
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
25626
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
25627
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
25628
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
25629
|
+
return {
|
|
25630
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
25631
|
+
progress * Math.PI * 1.8 +
|
|
25632
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
25633
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
25634
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
25635
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
25636
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
25637
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
25638
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
25639
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
25640
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
25641
|
+
};
|
|
25642
|
+
});
|
|
25643
|
+
}
|
|
25644
|
+
/**
|
|
25645
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
25646
|
+
*
|
|
25647
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25648
|
+
*/
|
|
25649
|
+
function createBlobbySkinSpots(createRandom) {
|
|
25650
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
25651
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
25652
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
25653
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
25654
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
25655
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
25656
|
+
}));
|
|
25657
|
+
}
|
|
25658
|
+
/**
|
|
25659
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
25660
|
+
*
|
|
25661
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25662
|
+
*/
|
|
25663
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
25664
|
+
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));
|
|
25665
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
25666
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
25667
|
+
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
25668
|
+
context.fillStyle = glowGradient;
|
|
25669
|
+
context.fillRect(0, 0, size, size);
|
|
25670
|
+
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);
|
|
25671
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
25672
|
+
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
25673
|
+
context.fillStyle = lowerGradient;
|
|
25674
|
+
context.fillRect(0, 0, size, size);
|
|
25675
|
+
}
|
|
25676
|
+
/**
|
|
25677
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
25678
|
+
*
|
|
25679
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25680
|
+
*/
|
|
25681
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
25682
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
25683
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
25684
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
25685
|
+
const ry = size * 0.062;
|
|
25686
|
+
context.save();
|
|
25687
|
+
context.translate(cx, cy);
|
|
25688
|
+
context.scale(1, ry / rx);
|
|
25689
|
+
const blurRadius = rx * 1.42;
|
|
25690
|
+
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
25691
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
25692
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
25693
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
25694
|
+
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
25695
|
+
context.fillStyle = shadowGradient;
|
|
25696
|
+
context.beginPath();
|
|
25697
|
+
context.arc(0, 0, blurRadius, 0, Math.PI * 2);
|
|
25698
|
+
context.fill();
|
|
25699
|
+
context.restore();
|
|
25700
|
+
}
|
|
25701
|
+
/**
|
|
25702
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
25703
|
+
*
|
|
25704
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25705
|
+
*/
|
|
25706
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
25707
|
+
/**
|
|
25708
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
25709
|
+
*
|
|
25710
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25711
|
+
*/
|
|
25712
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
25713
|
+
/**
|
|
25714
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
25715
|
+
*
|
|
25716
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
25717
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
25718
|
+
* than re-evaluated for every patch corner.
|
|
25719
|
+
*
|
|
25720
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25721
|
+
*/
|
|
25722
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
25723
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
25724
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
25725
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
25726
|
+
const surfacePatches = [];
|
|
25727
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
25728
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
25729
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
25730
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
25731
|
+
}
|
|
25732
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
25733
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
25734
|
+
}
|
|
25735
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
25736
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
25737
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
25738
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
25739
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
25740
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
25741
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
25742
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
25743
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
25744
|
+
}
|
|
25745
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
25746
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
25747
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
25748
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
25749
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
25750
|
+
}
|
|
25751
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
25752
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
25753
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
25754
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
25755
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
25756
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
25757
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
25758
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
25759
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
25760
|
+
}
|
|
25761
|
+
}
|
|
25762
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
25763
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
25764
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
25765
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
25766
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
25767
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
25768
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
25769
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
25770
|
+
const transformedCorners = [
|
|
25771
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
25772
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
25773
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
25774
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
25775
|
+
];
|
|
25776
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
25777
|
+
if (surfaceNormal.z <= 0.006) {
|
|
25778
|
+
continue;
|
|
25779
|
+
}
|
|
25780
|
+
const projectedCorners = [
|
|
25781
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
25782
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
25783
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
25784
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
25785
|
+
];
|
|
25786
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
25787
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
25788
|
+
surfacePatches.push({
|
|
25789
|
+
corners: projectedCorners,
|
|
25790
|
+
averageDepth: (transformedCorners[0].z +
|
|
25791
|
+
transformedCorners[1].z +
|
|
25792
|
+
transformedCorners[2].z +
|
|
25793
|
+
transformedCorners[3].z) /
|
|
25794
|
+
4,
|
|
25795
|
+
lightIntensity,
|
|
25796
|
+
rimLightIntensity,
|
|
25797
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
25798
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
25799
|
+
});
|
|
25800
|
+
}
|
|
25801
|
+
}
|
|
25802
|
+
return surfacePatches;
|
|
25803
|
+
}
|
|
25804
|
+
/**
|
|
25805
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
25806
|
+
*
|
|
25807
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25808
|
+
*/
|
|
25809
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
25810
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
25811
|
+
}
|
|
25812
|
+
/**
|
|
25813
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
25814
|
+
*
|
|
25815
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
25816
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
25817
|
+
* of `latitudePatchCount * 4` times.
|
|
25818
|
+
*
|
|
25819
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25820
|
+
*/
|
|
25821
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
25822
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
25823
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
25824
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
25825
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
25826
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
25827
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
25828
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
25829
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
25830
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
25831
|
+
animationPhase * 0.6 +
|
|
25832
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
25833
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
25834
|
+
(0.32 + lowerBlend * 0.7);
|
|
25835
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
25836
|
+
lowerBlend *
|
|
25837
|
+
tentacleInfluence.core *
|
|
25838
|
+
tentacleInfluence.swayScale;
|
|
25839
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
25840
|
+
lowerBlend *
|
|
25841
|
+
tentacleInfluence.core *
|
|
25842
|
+
tentacleInfluence.swayScale *
|
|
25843
|
+
0.42;
|
|
25844
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
25845
|
+
tipBlend *
|
|
25846
|
+
tentacleInfluence.core *
|
|
25847
|
+
tentacleInfluence.curlScale *
|
|
25848
|
+
0.36;
|
|
25849
|
+
const horizontalScale = 1.06 +
|
|
25850
|
+
mantleRipple +
|
|
25851
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
25852
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
25853
|
+
upperBlend * 0.08;
|
|
25854
|
+
const depthScale = 1.08 +
|
|
25855
|
+
upperBlend * 0.16 +
|
|
25856
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
25857
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
25858
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
25859
|
+
const tentacleTubeRadius = lowerBlend *
|
|
25860
|
+
tentacleInfluence.core *
|
|
25861
|
+
(0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) *
|
|
25862
|
+
radiusX;
|
|
25863
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
25864
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
25865
|
+
const lowerDrop = lowerBlend *
|
|
25866
|
+
radiusY *
|
|
25867
|
+
(0.2 +
|
|
25868
|
+
tentacleInfluence.core *
|
|
25869
|
+
(0.42 +
|
|
25870
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
25871
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
25872
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
25873
|
+
return {
|
|
25874
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
25875
|
+
combinedTentacleSway +
|
|
25876
|
+
tentacleCurl * radiusX * 0.18,
|
|
25877
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
25878
|
+
upperBlend * radiusY * 0.12 +
|
|
25879
|
+
lowerDrop +
|
|
25880
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
25881
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
25882
|
+
lowerBlend *
|
|
25883
|
+
tipBlend *
|
|
25884
|
+
tentacleInfluence.core *
|
|
25885
|
+
radiusY *
|
|
25886
|
+
0.04,
|
|
25887
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
25888
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
25889
|
+
lowerBlend *
|
|
25890
|
+
tentacleInfluence.core *
|
|
25891
|
+
radiusZ *
|
|
25892
|
+
0.044 +
|
|
25893
|
+
tentacleCurl * radiusZ * 0.14,
|
|
25894
|
+
};
|
|
25895
|
+
}
|
|
25896
|
+
/**
|
|
25897
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
25898
|
+
*
|
|
25899
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25900
|
+
*/
|
|
25901
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
25902
|
+
let totalWeight = 0;
|
|
25903
|
+
let weightedSin = 0;
|
|
25904
|
+
let weightedCos = 0;
|
|
25905
|
+
let weightedWidthScale = 0;
|
|
25906
|
+
let weightedLengthScale = 0;
|
|
25907
|
+
let weightedSwayScale = 0;
|
|
25908
|
+
let weightedDepthScale = 0;
|
|
25909
|
+
let weightedCurlScale = 0;
|
|
25910
|
+
let weightedPrimaryPhase = 0;
|
|
25911
|
+
let weightedSecondaryPhase = 0;
|
|
25912
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
25913
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
25914
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
25915
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
25916
|
+
totalWeight += weight;
|
|
25917
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
25918
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
25919
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
25920
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
25921
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
25922
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
25923
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
25924
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
25925
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
25926
|
+
}
|
|
25927
|
+
if (totalWeight < 0.0001) {
|
|
25928
|
+
return {
|
|
25929
|
+
core: 0,
|
|
25930
|
+
centerLongitude: longitude,
|
|
25931
|
+
widthScale: 1,
|
|
25932
|
+
lengthScale: 1,
|
|
25933
|
+
swayScale: 1,
|
|
25934
|
+
depthScale: 1,
|
|
25935
|
+
curlScale: 1,
|
|
25936
|
+
primaryPhase: 0,
|
|
25937
|
+
secondaryPhase: 0,
|
|
25938
|
+
};
|
|
25939
|
+
}
|
|
25940
|
+
return {
|
|
25941
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
25942
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
25943
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
25944
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
25945
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
25946
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
25947
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
25948
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
25949
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
25950
|
+
};
|
|
25951
|
+
}
|
|
25952
|
+
/**
|
|
25953
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
25954
|
+
*
|
|
25955
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25956
|
+
*/
|
|
25957
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
25958
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
25959
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
25960
|
+
animationPhase +
|
|
25961
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
25962
|
+
1) /
|
|
25963
|
+
2);
|
|
25964
|
+
}
|
|
25965
|
+
/**
|
|
25966
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
25967
|
+
*
|
|
25968
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25969
|
+
*/
|
|
25970
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
25971
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
25972
|
+
if (tonalProgress < 0.12) {
|
|
25973
|
+
return palette.highlight;
|
|
25974
|
+
}
|
|
25975
|
+
if (tonalProgress < 0.3) {
|
|
25976
|
+
return palette.secondary;
|
|
25977
|
+
}
|
|
25978
|
+
if (tonalProgress < 0.72) {
|
|
25979
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
25980
|
+
}
|
|
25981
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
25982
|
+
}
|
|
25983
|
+
/**
|
|
25984
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
25985
|
+
*
|
|
25986
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
25987
|
+
*/
|
|
25988
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
25989
|
+
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
25990
|
+
if (surfacePatch.lightIntensity > 0) {
|
|
25991
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
25992
|
+
}
|
|
25993
|
+
else if (surfacePatch.lightIntensity < 0) {
|
|
25994
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
25995
|
+
}
|
|
25996
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
25997
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
25998
|
+
}
|
|
25999
|
+
context.save();
|
|
26000
|
+
context.beginPath();
|
|
26001
|
+
context.moveTo(surfacePatch.corners[0].x, surfacePatch.corners[0].y);
|
|
26002
|
+
for (let cornerIndex = 1; cornerIndex < surfacePatch.corners.length; cornerIndex++) {
|
|
26003
|
+
context.lineTo(surfacePatch.corners[cornerIndex].x, surfacePatch.corners[cornerIndex].y);
|
|
26004
|
+
}
|
|
26005
|
+
context.closePath();
|
|
26006
|
+
context.strokeStyle = surfacePatch.outlineColor;
|
|
26007
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
26008
|
+
context.lineJoin = 'round';
|
|
26009
|
+
context.stroke();
|
|
26010
|
+
context.restore();
|
|
26011
|
+
}
|
|
26012
|
+
/**
|
|
26013
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
26014
|
+
*
|
|
26015
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26016
|
+
*/
|
|
26017
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
26018
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
26019
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
26020
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
26021
|
+
context.save();
|
|
26022
|
+
context.lineCap = 'round';
|
|
26023
|
+
context.lineJoin = 'round';
|
|
26024
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
26025
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
26026
|
+
const projectedPoints = [];
|
|
26027
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
26028
|
+
const progress = sampleIndex / 8;
|
|
26029
|
+
const latitude = -0.48 + progress * 0.78;
|
|
26030
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
26031
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
26032
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
26033
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
26034
|
+
}
|
|
26035
|
+
}
|
|
26036
|
+
if (projectedPoints.length < 3) {
|
|
26037
|
+
continue;
|
|
26038
|
+
}
|
|
26039
|
+
context.beginPath();
|
|
26040
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
26041
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
26042
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
26043
|
+
}
|
|
26044
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
26045
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
26046
|
+
context.stroke();
|
|
26047
|
+
}
|
|
26048
|
+
context.restore();
|
|
26049
|
+
}
|
|
26050
|
+
/**
|
|
26051
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
26052
|
+
*
|
|
26053
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26054
|
+
*/
|
|
26055
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
26056
|
+
const { surfaceOptions, size } = options;
|
|
26057
|
+
const { timeMs } = surfaceOptions;
|
|
26058
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
26059
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
26060
|
+
continue;
|
|
26061
|
+
}
|
|
26062
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
26063
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
26064
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
26065
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
26066
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
26067
|
+
...options,
|
|
26068
|
+
latitude,
|
|
26069
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
26070
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
26071
|
+
});
|
|
26072
|
+
}
|
|
26073
|
+
}
|
|
26074
|
+
}
|
|
26075
|
+
/**
|
|
26076
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
26077
|
+
*
|
|
26078
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26079
|
+
*/
|
|
26080
|
+
function drawBlobbySkinSpots(options) {
|
|
26081
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
26082
|
+
for (const skinSpot of skinSpots) {
|
|
26083
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
26084
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
26085
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
26086
|
+
continue;
|
|
26087
|
+
}
|
|
26088
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
26089
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
26090
|
+
context.save();
|
|
26091
|
+
context.beginPath();
|
|
26092
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
26093
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
26094
|
+
context.fill();
|
|
26095
|
+
context.restore();
|
|
26096
|
+
}
|
|
26097
|
+
}
|
|
26098
|
+
/**
|
|
26099
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
26100
|
+
*
|
|
26101
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26102
|
+
*/
|
|
26103
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
26104
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
26105
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
26106
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
26107
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
26108
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
26109
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
26110
|
+
return;
|
|
26111
|
+
}
|
|
26112
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
26113
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
26114
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
26115
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
26116
|
+
radiusScale *
|
|
26117
|
+
0.78, size * 0.003, size * 0.02);
|
|
26118
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
26119
|
+
radiusScale *
|
|
26120
|
+
0.54, size * 0.0024, size * 0.015);
|
|
26121
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
26122
|
+
context.save();
|
|
26123
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
26124
|
+
context.rotate(rotation);
|
|
26125
|
+
context.beginPath();
|
|
26126
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
26127
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
26128
|
+
context.fill();
|
|
26129
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
26130
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
26131
|
+
context.stroke();
|
|
26132
|
+
context.restore();
|
|
26133
|
+
}
|
|
26134
|
+
/**
|
|
26135
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
26136
|
+
*
|
|
26137
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26138
|
+
*/
|
|
26139
|
+
function drawBlobbyContinuousGloss(options) {
|
|
26140
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
26141
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
26142
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
26143
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
26144
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
26145
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
26146
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
26147
|
+
return;
|
|
26148
|
+
}
|
|
26149
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
26150
|
+
const glossRadius = size * 0.058;
|
|
26151
|
+
context.save();
|
|
26152
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
26153
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
26154
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
26155
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
26156
|
+
context.fillStyle = glossGradient;
|
|
26157
|
+
context.beginPath();
|
|
26158
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
26159
|
+
context.fill();
|
|
26160
|
+
context.restore();
|
|
26161
|
+
}
|
|
26162
|
+
/**
|
|
26163
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
26164
|
+
*
|
|
26165
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26166
|
+
*/
|
|
26167
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
26168
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
26169
|
+
}
|
|
26170
|
+
/**
|
|
26171
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
26172
|
+
*
|
|
26173
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26174
|
+
*/
|
|
26175
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
26176
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
26177
|
+
return progress * progress * (3 - 2 * progress);
|
|
26178
|
+
}
|
|
26179
|
+
/**
|
|
26180
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
26181
|
+
*
|
|
26182
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
26183
|
+
*/
|
|
26184
|
+
function formatAlphaHex(opacity) {
|
|
26185
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
26186
|
+
.toString(16)
|
|
26187
|
+
.padStart(2, '0');
|
|
26188
|
+
}
|
|
26189
|
+
|
|
24635
26190
|
/* eslint-disable no-magic-numbers */
|
|
24636
26191
|
/**
|
|
24637
26192
|
* Octopus avatar visual.
|
|
@@ -25402,6 +26957,8 @@
|
|
|
25402
26957
|
octopus3AvatarVisual,
|
|
25403
26958
|
octopus3dAvatarVisual,
|
|
25404
26959
|
octopus3d2AvatarVisual,
|
|
26960
|
+
octopus3d3AvatarVisual,
|
|
26961
|
+
octopus3d4AvatarVisual,
|
|
25405
26962
|
asciiOctopusAvatarVisual,
|
|
25406
26963
|
minecraftAvatarVisual,
|
|
25407
26964
|
minecraft2AvatarVisual,
|
|
@@ -26081,6 +27638,70 @@
|
|
|
26081
27638
|
}
|
|
26082
27639
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
26083
27640
|
|
|
27641
|
+
/**
|
|
27642
|
+
* META VISIBILITY commitment definition.
|
|
27643
|
+
*
|
|
27644
|
+
* The `META VISIBILITY` commitment stores whether an agent is public, private, or unlisted.
|
|
27645
|
+
* Agents Server mirrors this value into the database for efficient filtering, but the book
|
|
27646
|
+
* commitment remains the editable source of truth.
|
|
27647
|
+
*
|
|
27648
|
+
* @private Metadata-only commitment used by Agents Server.
|
|
27649
|
+
*/
|
|
27650
|
+
class MetaVisibilityCommitmentDefinition extends BaseCommitmentDefinition {
|
|
27651
|
+
constructor() {
|
|
27652
|
+
super('META VISIBILITY');
|
|
27653
|
+
}
|
|
27654
|
+
/**
|
|
27655
|
+
* Short one-line description of META VISIBILITY.
|
|
27656
|
+
*/
|
|
27657
|
+
get description() {
|
|
27658
|
+
return 'Set whether the agent is private, unlisted, or public.';
|
|
27659
|
+
}
|
|
27660
|
+
/**
|
|
27661
|
+
* Icon for this commitment.
|
|
27662
|
+
*/
|
|
27663
|
+
get icon() {
|
|
27664
|
+
return '👁️';
|
|
27665
|
+
}
|
|
27666
|
+
/**
|
|
27667
|
+
* Markdown documentation for META VISIBILITY commitment.
|
|
27668
|
+
*/
|
|
27669
|
+
get documentation() {
|
|
27670
|
+
return _spaceTrim.spaceTrim(`
|
|
27671
|
+
# META VISIBILITY
|
|
27672
|
+
|
|
27673
|
+
Sets the agent visibility used by Agents Server.
|
|
27674
|
+
|
|
27675
|
+
## Allowed values
|
|
27676
|
+
|
|
27677
|
+
- \`PRIVATE\` - accessible only to signed-in users with access.
|
|
27678
|
+
- \`UNLISTED\` - accessible by direct link but hidden from public listings.
|
|
27679
|
+
- \`PUBLIC\` - visible in public listings and accessible by anyone.
|
|
27680
|
+
|
|
27681
|
+
## Key aspects
|
|
27682
|
+
|
|
27683
|
+
- Does not modify the agent's behavior, system message, or tools.
|
|
27684
|
+
- Whitespace and letter case are normalized when persisted.
|
|
27685
|
+
- If multiple \`META VISIBILITY\` commitments are present, persistence keeps one normalized value.
|
|
27686
|
+
- Agents Server mirrors the value into the database for filtering, but the book is the source of truth.
|
|
27687
|
+
|
|
27688
|
+
## Example
|
|
27689
|
+
|
|
27690
|
+
\`\`\`book
|
|
27691
|
+
Helpful Assistant
|
|
27692
|
+
|
|
27693
|
+
GOAL Be helpful and friendly.
|
|
27694
|
+
META VISIBILITY PUBLIC
|
|
27695
|
+
\`\`\`
|
|
27696
|
+
`);
|
|
27697
|
+
}
|
|
27698
|
+
applyToAgentModelRequirements(requirements, content) {
|
|
27699
|
+
// META VISIBILITY is metadata only and does not alter model requirements.
|
|
27700
|
+
return requirements;
|
|
27701
|
+
}
|
|
27702
|
+
}
|
|
27703
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
27704
|
+
|
|
26084
27705
|
/**
|
|
26085
27706
|
* META VOICE commitment definition
|
|
26086
27707
|
*
|
|
@@ -27690,7 +29311,15 @@
|
|
|
27690
29311
|
* Builds the teammate request text, optionally including context.
|
|
27691
29312
|
*/
|
|
27692
29313
|
function buildTeammateRequest(message, context) {
|
|
27693
|
-
|
|
29314
|
+
if (!context) {
|
|
29315
|
+
return message;
|
|
29316
|
+
}
|
|
29317
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
29318
|
+
${block(message)}
|
|
29319
|
+
|
|
29320
|
+
Context:
|
|
29321
|
+
${block(context)}
|
|
29322
|
+
`);
|
|
27694
29323
|
}
|
|
27695
29324
|
/**
|
|
27696
29325
|
* Builds a minimal chat prompt for teammate calls.
|
|
@@ -31516,7 +33145,11 @@
|
|
|
31516
33145
|
const lineRangedContent = applyOptionalLineRange(decodedContent, args.startLine, args.endLine);
|
|
31517
33146
|
const wasCharacterTruncated = lineRangedContent.length > MAX_PROJECT_FILE_CONTENT_CHARACTERS;
|
|
31518
33147
|
const contentToReturn = wasCharacterTruncated
|
|
31519
|
-
?
|
|
33148
|
+
? _spaceTrim.spaceTrim((block) => `
|
|
33149
|
+
${block(lineRangedContent.slice(0, MAX_PROJECT_FILE_CONTENT_CHARACTERS))}
|
|
33150
|
+
|
|
33151
|
+
[...truncated...]
|
|
33152
|
+
`)
|
|
31520
33153
|
: lineRangedContent;
|
|
31521
33154
|
const wasTruncated = decoded.isTruncated || wasCharacterTruncated;
|
|
31522
33155
|
if (wasCharacterTruncated) {
|
|
@@ -32292,6 +33925,40 @@
|
|
|
32292
33925
|
}
|
|
32293
33926
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
32294
33927
|
|
|
33928
|
+
/**
|
|
33929
|
+
* Supported visibility states for persisted agents.
|
|
33930
|
+
*
|
|
33931
|
+
* @public exported from `@promptbook/core`
|
|
33932
|
+
*/
|
|
33933
|
+
const AGENT_VISIBILITY_VALUES = ['PRIVATE', 'UNLISTED', 'PUBLIC'];
|
|
33934
|
+
/**
|
|
33935
|
+
* Returns `true` when the value is one of supported visibility states.
|
|
33936
|
+
*
|
|
33937
|
+
* @param value - Raw value to validate.
|
|
33938
|
+
* @returns Whether the value is a valid `AgentVisibility`.
|
|
33939
|
+
*
|
|
33940
|
+
* @public exported from `@promptbook/core`
|
|
33941
|
+
*/
|
|
33942
|
+
function isAgentVisibility(value) {
|
|
33943
|
+
return typeof value === 'string' && AGENT_VISIBILITY_VALUES.includes(value);
|
|
33944
|
+
}
|
|
33945
|
+
/**
|
|
33946
|
+
* Normalizes raw visibility text into a supported value.
|
|
33947
|
+
*
|
|
33948
|
+
* @param value - Raw visibility value.
|
|
33949
|
+
* @returns Normalized visibility, or `null` when invalid.
|
|
33950
|
+
*
|
|
33951
|
+
* @public exported from `@promptbook/core`
|
|
33952
|
+
*/
|
|
33953
|
+
function normalizeAgentVisibility(value) {
|
|
33954
|
+
if (typeof value !== 'string') {
|
|
33955
|
+
return null;
|
|
33956
|
+
}
|
|
33957
|
+
const normalized = value.trim().toUpperCase();
|
|
33958
|
+
return isAgentVisibility(normalized) ? normalized : null;
|
|
33959
|
+
}
|
|
33960
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
33961
|
+
|
|
32295
33962
|
/**
|
|
32296
33963
|
* Maximum allowed source length for create-agent payloads.
|
|
32297
33964
|
*
|
|
@@ -34545,6 +36212,7 @@
|
|
|
34545
36212
|
new MetaDomainCommitmentDefinition(),
|
|
34546
36213
|
new MetaDisclaimerCommitmentDefinition(),
|
|
34547
36214
|
new MetaInputPlaceholderCommitmentDefinition(),
|
|
36215
|
+
new MetaVisibilityCommitmentDefinition(),
|
|
34548
36216
|
new MetaCommitmentDefinition(),
|
|
34549
36217
|
new MetaVoiceCommitmentDefinition(),
|
|
34550
36218
|
new NoteCommitmentDefinition('NOTE'),
|
|
@@ -35005,11 +36673,19 @@
|
|
|
35005
36673
|
try {
|
|
35006
36674
|
const json = JSON.parse(content);
|
|
35007
36675
|
const formattedJson = JSON.stringify(json, null, 4);
|
|
35008
|
-
return
|
|
36676
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
36677
|
+
\`\`\`json
|
|
36678
|
+
${block(formattedJson)}
|
|
36679
|
+
\`\`\`
|
|
36680
|
+
`);
|
|
35009
36681
|
}
|
|
35010
36682
|
catch (error) {
|
|
35011
36683
|
// If JSON is invalid, still import it but maybe not as pretty JSON
|
|
35012
|
-
return
|
|
36684
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
36685
|
+
\`\`\`json
|
|
36686
|
+
${block(content)}
|
|
36687
|
+
\`\`\`
|
|
36688
|
+
`);
|
|
35013
36689
|
}
|
|
35014
36690
|
},
|
|
35015
36691
|
};
|
|
@@ -35033,7 +36709,11 @@
|
|
|
35033
36709
|
import(content, mimeType) {
|
|
35034
36710
|
const extension = mimeTypeToExtension(mimeType);
|
|
35035
36711
|
const codeBlockType = extension || 'txt';
|
|
35036
|
-
return
|
|
36712
|
+
return _spaceTrim.spaceTrim((block) => `
|
|
36713
|
+
\`\`\`${codeBlockType}
|
|
36714
|
+
${block(content)}
|
|
36715
|
+
\`\`\`
|
|
36716
|
+
`);
|
|
35037
36717
|
},
|
|
35038
36718
|
};
|
|
35039
36719
|
|
|
@@ -35280,11 +36960,19 @@
|
|
|
35280
36960
|
const examples = [];
|
|
35281
36961
|
const initialMessage = (_a = parseResult.commitments.find((commitment) => commitment.type === 'INITIAL MESSAGE')) === null || _a === void 0 ? void 0 : _a.content;
|
|
35282
36962
|
if (initialMessage) {
|
|
35283
|
-
examples.push(
|
|
36963
|
+
examples.push(_spaceTrim.spaceTrim((block) => `
|
|
36964
|
+
**Agent:**
|
|
36965
|
+
${block(initialMessage)}
|
|
36966
|
+
`));
|
|
35284
36967
|
}
|
|
35285
36968
|
if (samples && samples.length > 0) {
|
|
35286
36969
|
for (const sample of samples) {
|
|
35287
|
-
examples.push(
|
|
36970
|
+
examples.push(_spaceTrim.spaceTrim((block) => `
|
|
36971
|
+
**User:** ${block(String(sample.question))}
|
|
36972
|
+
|
|
36973
|
+
**Agent:**
|
|
36974
|
+
${block(sample.answer)}
|
|
36975
|
+
`));
|
|
35288
36976
|
}
|
|
35289
36977
|
}
|
|
35290
36978
|
return examples;
|
|
@@ -35878,6 +37566,7 @@
|
|
|
35878
37566
|
'META COLOR': applyMetaColorContent,
|
|
35879
37567
|
'META FONT': applyMetaFontContent,
|
|
35880
37568
|
'META VOICE': applyMetaVoiceContent,
|
|
37569
|
+
'META VISIBILITY': applyMetaVisibilityContent,
|
|
35881
37570
|
};
|
|
35882
37571
|
/**
|
|
35883
37572
|
* Applies META-style commitments that mutate parsed profile metadata.
|
|
@@ -35907,6 +37596,10 @@
|
|
|
35907
37596
|
applyMetaAvatarContent(state, metaValue);
|
|
35908
37597
|
return;
|
|
35909
37598
|
}
|
|
37599
|
+
if (metaTypeRaw.toUpperCase() === 'VISIBILITY') {
|
|
37600
|
+
applyMetaVisibilityContent(state, metaValue);
|
|
37601
|
+
return;
|
|
37602
|
+
}
|
|
35910
37603
|
const metaType = normalizeTo_camelCase(metaTypeRaw);
|
|
35911
37604
|
state.meta[metaType] = metaValue;
|
|
35912
37605
|
}
|
|
@@ -35983,6 +37676,15 @@
|
|
|
35983
37676
|
function applyMetaVoiceContent(state, content) {
|
|
35984
37677
|
state.meta.voice = _spaceTrim.spaceTrim(content);
|
|
35985
37678
|
}
|
|
37679
|
+
/**
|
|
37680
|
+
* Applies META VISIBILITY content into the normalized `meta.visibility` field.
|
|
37681
|
+
*/
|
|
37682
|
+
function applyMetaVisibilityContent(state, content) {
|
|
37683
|
+
const visibility = normalizeAgentVisibility(content);
|
|
37684
|
+
if (visibility) {
|
|
37685
|
+
state.meta.visibility = visibility;
|
|
37686
|
+
}
|
|
37687
|
+
}
|
|
35986
37688
|
/**
|
|
35987
37689
|
* Normalizes the separator in the content
|
|
35988
37690
|
*
|
|
@@ -39809,7 +41511,12 @@
|
|
|
39809
41511
|
* @private internal function of `$registeredLlmToolsMessage`
|
|
39810
41512
|
*/
|
|
39811
41513
|
function createUsedEnvMessage() {
|
|
39812
|
-
return $usedEnvFilename === null
|
|
41514
|
+
return $usedEnvFilename === null
|
|
41515
|
+
? `Unknown \`.env\` file`
|
|
41516
|
+
: _spaceTrim.spaceTrim(`
|
|
41517
|
+
Used \`.env\` file:
|
|
41518
|
+
${$usedEnvFilename}
|
|
41519
|
+
`);
|
|
39813
41520
|
}
|
|
39814
41521
|
// TODO: [®] DRY Register logic
|
|
39815
41522
|
// TODO: [🧠][⚛] Maybe pass env as argument
|
|
@@ -40349,7 +42056,7 @@
|
|
|
40349
42056
|
*
|
|
40350
42057
|
* It looks for environment variables:
|
|
40351
42058
|
* - `process.env.OPENAI_API_KEY`
|
|
40352
|
-
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
42059
|
+
* - `process.env.ANTHROPIC_API_KEY` (or the deprecated `process.env.ANTHROPIC_CLAUDE_API_KEY`)
|
|
40353
42060
|
* - ...
|
|
40354
42061
|
*
|
|
40355
42062
|
* @see Environment variables documentation or .env file for required variables.
|
|
@@ -40449,7 +42156,7 @@
|
|
|
40449
42156
|
*
|
|
40450
42157
|
* It looks for environment variables:
|
|
40451
42158
|
* - `process.env.OPENAI_API_KEY`
|
|
40452
|
-
* - `process.env.ANTHROPIC_CLAUDE_API_KEY`
|
|
42159
|
+
* - `process.env.ANTHROPIC_API_KEY` (or the deprecated `process.env.ANTHROPIC_CLAUDE_API_KEY`)
|
|
40453
42160
|
* - ...
|
|
40454
42161
|
*
|
|
40455
42162
|
* @param options Configuration options for the LLM tools
|
|
@@ -48879,7 +50586,9 @@
|
|
|
48879
50586
|
if (!executionTools || !executionTools.script) {
|
|
48880
50587
|
throw new PipelineExecutionError(`Model requested tools but no executionTools.script were provided in OpenAiAgentKitExecutionTools options`);
|
|
48881
50588
|
}
|
|
48882
|
-
return Array.isArray(executionTools.script)
|
|
50589
|
+
return Array.isArray(executionTools.script)
|
|
50590
|
+
? executionTools.script
|
|
50591
|
+
: [executionTools.script];
|
|
48883
50592
|
}
|
|
48884
50593
|
/**
|
|
48885
50594
|
* Resolves the assistant-visible AgentKit tool response while preserving structured tool result data.
|
|
@@ -49301,7 +51010,7 @@
|
|
|
49301
51010
|
* Computes one stable hash from a JSON-serializable value.
|
|
49302
51011
|
*/
|
|
49303
51012
|
function computeJsonHash$1(value) {
|
|
49304
|
-
return
|
|
51013
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
49305
51014
|
}
|
|
49306
51015
|
/**
|
|
49307
51016
|
* Handles OpenAI AgentKit-backed executions for `AgentLlmExecutionTools`.
|
|
@@ -49459,7 +51168,7 @@
|
|
|
49459
51168
|
* Computes one stable hash from a JSON-serializable value.
|
|
49460
51169
|
*/
|
|
49461
51170
|
function computeJsonHash(value) {
|
|
49462
|
-
return
|
|
51171
|
+
return CryptoJS__default["default"].SHA256(JSON.stringify(value)).toString();
|
|
49463
51172
|
}
|
|
49464
51173
|
/**
|
|
49465
51174
|
* Removes assistant-managed requirements before the prompt is executed via OpenAI Assistants.
|
|
@@ -49795,7 +51504,11 @@
|
|
|
49795
51504
|
};
|
|
49796
51505
|
}
|
|
49797
51506
|
return {
|
|
49798
|
-
content:
|
|
51507
|
+
content: _spaceTrim.spaceTrim((block) => `
|
|
51508
|
+
${block(content.slice(0, Math.max(0, maxCharacters)))}
|
|
51509
|
+
|
|
51510
|
+
[...truncated...]
|
|
51511
|
+
`),
|
|
49799
51512
|
isTruncated: true,
|
|
49800
51513
|
};
|
|
49801
51514
|
}
|
|
@@ -50261,7 +51974,7 @@
|
|
|
50261
51974
|
* Returns a virtual model name representing the agent behavior.
|
|
50262
51975
|
*/
|
|
50263
51976
|
get modelName() {
|
|
50264
|
-
const hash =
|
|
51977
|
+
const hash = CryptoJS__default["default"].SHA256(hexEncoder__default["default"].parse(this.options.agentSource)).toString( /* hex */);
|
|
50265
51978
|
const agentId = hash.substring(0, 10);
|
|
50266
51979
|
return (normalizeToKebabCase(this.title) + '-' + agentId);
|
|
50267
51980
|
}
|