@promptbook/browser 0.113.0-8 → 0.113.0-9
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/esm/index.es.js +1813 -1804
- package/esm/index.es.js.map +1 -1
- package/esm/src/_packages/types.index.d.ts +2 -2
- package/esm/src/avatars/renderAvatarVisualAsciiArt.d.ts +24 -1
- package/esm/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +1 -1
- package/esm/src/book-components/BookEditor/BookEditorMonacoUploadPanel.d.ts +1 -1
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/bookEditorMonacoUploadTypes.d.ts +120 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/clearScheduledTimer.d.ts +6 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/enqueueFilesForUpload.d.ts +26 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadEditorSync.d.ts +23 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadItemsState.d.ts +21 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadProgressQueue.d.ts +12 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadQueue.d.ts +27 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads/useCompletedUploadsAutoClear.d.ts +12 -0
- package/esm/src/book-components/BookEditor/useBookEditorMonacoUploads.d.ts +5 -58
- package/esm/src/book-components/Chat/Chat/ChatImageAttachmentModal.d.ts +25 -0
- package/esm/src/book-components/Chat/Chat/ChatInputArea.d.ts +3 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageAttachments.d.ts +27 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +1 -1
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +1 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +8 -0
- package/esm/src/book-components/Chat/Chat/useChatInputAreaComposer.d.ts +12 -0
- package/esm/src/book-components/Chat/Chat/useChatInputAreaComposer.draftMessage.test.d.ts +2 -0
- package/esm/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +1 -1
- package/esm/src/book-components/Chat/utils/parseMessageButtons.d.ts +16 -2
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/AGENTS_SERVER_BUILD_CACHE_FILENAME.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/AGENTS_SERVER_BUILD_CACHE_VERSION.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/AGENTS_SERVER_NEXT_BUILD_ID_FILENAME.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/AgentsServerBuildCache.d.ts +10 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/AgentsServerBuildCacheOptions.d.ts +10 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/NODE_MODULES_DIRECTORY_NAME.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/PreparedAgentsServerRuntime.d.ts +18 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/createAgentsServerBuildSourceFingerprint.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/createAgentsServerRuntimeEnvironment.d.ts +28 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/ensureAgentsServerBuild.d.ts +19 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/getAgentsServerBuildInputRelativePaths.d.ts +11 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerAppPath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerAppPathMaterialized.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerBuildCacheCurrent.d.ts +7 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerBuildInputTestFile.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isExcludedAgentsServerBuildInputDirectoryName.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isExcludedAgentsServerBuildInputPath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isExcludedAgentsServerRuntimeSourcePath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isFile.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/isPathInsideNodeModules.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/normalizeAgentsServerBuildInputPath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/prepareAgentsServerRuntime.d.ts +9 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/readAgentsServerBuildCache.d.ts +8 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/resolveAgentsServerAppPath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/resolveAgentsServerBuildAppPath.d.ts +9 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/resolveAgentsServerBuildOutputPath.d.ts +7 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/resolveNextCliPath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/resolveNodeModulesPath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/runNextBuild.d.ts +12 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/shouldCopyAgentsServerRuntimePath.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/synchronizeMaterializedAgentsServerRuntime.d.ts +11 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer/writeAgentsServerBuildCache.d.ts +7 -0
- package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +8 -109
- package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +1 -1
- package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +1 -1
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerChildEnvironment.d.ts +16 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerLogStreams.d.ts +30 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerRuntimePaths.d.ts +19 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerSupervisorState.d.ts +37 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/LocalAgentRunnerLimits.d.ts +9 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/PreparedAgentsServerLaunch.d.ts +13 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/StartAgentsServerOptions.d.ts +24 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/createInternalRouteErrorMessage.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/createLocalAgentRunOptions.d.ts +9 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/forwardChildOutput.d.ts +13 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/loadAgentsServerProjectEnvironment.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/prepareAgentsServerLaunch.d.ts +18 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/readInternalRouteErrorDetails.d.ts +6 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/resolveAgentsServerChildHostname.d.ts +7 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/startNextServer.d.ts +20 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/startUserChatJobWorkerPump.d.ts +15 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/stopChildProcess.d.ts +8 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer/waitForLocalAgentRunnerLimits.d.ts +16 -0
- package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +3 -30
- package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +1 -1
- package/esm/src/collection/agent-collection/CreateAgentInput.d.ts +1 -1
- package/esm/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +1 -1
- package/esm/src/commitments/META/META.d.ts +2 -2
- package/esm/src/commitments/META_AVATAR/META_AVATAR.d.ts +9 -4
- package/esm/src/commitments/index.d.ts +1 -1
- package/esm/src/conversion/validation/validatePipeline/createPipelineValidationContext.d.ts +16 -0
- package/esm/src/conversion/validation/validatePipeline/validatePipelineCollectionsStructure.d.ts +7 -0
- package/esm/src/conversion/validation/validatePipeline/validatePipelineDependencyResolution.d.ts +7 -0
- package/esm/src/conversion/validation/validatePipeline/validatePipelineMetadata.d.ts +7 -0
- package/esm/src/conversion/validation/validatePipeline/validatePipelineParameters.d.ts +7 -0
- package/esm/src/conversion/validation/validatePipeline/validatePipelineTasks.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/RegisteredLlmToolsMessageContext.d.ts +46 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/USED_ENV_FILENAME.d.ts +11 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolConfigurationStatusMessage.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolEntryKey.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolInstallationStatusMessage.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolStatus.d.ts +14 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolsMessageContext.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/getAvailableRegisteredLlmToolsEnvironmentVariables.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/getRegisteredLlmToolRegisters.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/hasRegisteredLlmToolEntry.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/listRegisteredLlmToolEntries.d.ts +9 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/listRegisteredLlmToolStatuses.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderAvailableRegisteredLlmTools.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderNoRegisteredLlmToolsMessage.d.ts +7 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderRegisteredLlmToolsMessage.d.ts +8 -0
- package/esm/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderRelevantRegisteredLlmToolsEnvironmentVariables.d.ts +7 -0
- package/esm/src/utils/agents/resolveAgentAvatarImageUrl.d.ts +2 -2
- package/esm/src/utils/color/Color.d.ts +1 -1
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/umd/index.umd.js +1813 -1804
- package/umd/index.umd.js.map +1 -1
- package/umd/src/_packages/types.index.d.ts +2 -2
- package/umd/src/avatars/renderAvatarVisualAsciiArt.d.ts +24 -1
- package/umd/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +1 -1
- package/umd/src/book-components/BookEditor/BookEditorMonacoUploadPanel.d.ts +1 -1
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/bookEditorMonacoUploadTypes.d.ts +120 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/clearScheduledTimer.d.ts +6 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/enqueueFilesForUpload.d.ts +26 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadEditorSync.d.ts +23 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadItemsState.d.ts +21 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadProgressQueue.d.ts +12 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/useBookEditorMonacoUploadQueue.d.ts +27 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads/useCompletedUploadsAutoClear.d.ts +12 -0
- package/umd/src/book-components/BookEditor/useBookEditorMonacoUploads.d.ts +5 -58
- package/umd/src/book-components/Chat/Chat/ChatImageAttachmentModal.d.ts +25 -0
- package/umd/src/book-components/Chat/Chat/ChatInputArea.d.ts +3 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageAttachments.d.ts +27 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +1 -1
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +1 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +8 -0
- package/umd/src/book-components/Chat/Chat/useChatInputAreaComposer.d.ts +12 -0
- package/umd/src/book-components/Chat/Chat/useChatInputAreaComposer.draftMessage.test.d.ts +2 -0
- package/umd/src/book-components/Chat/utils/isVisibleChatToolCall.d.ts +1 -1
- package/umd/src/book-components/Chat/utils/parseMessageButtons.d.ts +16 -2
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/AGENTS_SERVER_BUILD_CACHE_FILENAME.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/AGENTS_SERVER_BUILD_CACHE_VERSION.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/AGENTS_SERVER_NEXT_BUILD_ID_FILENAME.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/AgentsServerBuildCache.d.ts +10 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/AgentsServerBuildCacheOptions.d.ts +10 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/NODE_MODULES_DIRECTORY_NAME.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/PreparedAgentsServerRuntime.d.ts +18 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/createAgentsServerBuildSourceFingerprint.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/createAgentsServerRuntimeEnvironment.d.ts +28 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/ensureAgentsServerBuild.d.ts +19 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/getAgentsServerBuildInputRelativePaths.d.ts +11 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerAppPath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerAppPathMaterialized.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerBuildCacheCurrent.d.ts +7 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isAgentsServerBuildInputTestFile.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isExcludedAgentsServerBuildInputDirectoryName.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isExcludedAgentsServerBuildInputPath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isExcludedAgentsServerRuntimeSourcePath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isFile.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/isPathInsideNodeModules.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/normalizeAgentsServerBuildInputPath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/prepareAgentsServerRuntime.d.ts +9 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/readAgentsServerBuildCache.d.ts +8 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/resolveAgentsServerAppPath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/resolveAgentsServerBuildAppPath.d.ts +9 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/resolveAgentsServerBuildOutputPath.d.ts +7 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/resolveNextCliPath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/resolveNodeModulesPath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/runNextBuild.d.ts +12 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/shouldCopyAgentsServerRuntimePath.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/synchronizeMaterializedAgentsServerRuntime.d.ts +11 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer/writeAgentsServerBuildCache.d.ts +7 -0
- package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +8 -109
- package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +1 -1
- package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +1 -1
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerChildEnvironment.d.ts +16 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerLogStreams.d.ts +30 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerRuntimePaths.d.ts +19 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/AgentsServerSupervisorState.d.ts +37 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/LocalAgentRunnerLimits.d.ts +9 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/PreparedAgentsServerLaunch.d.ts +13 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/StartAgentsServerOptions.d.ts +24 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/createInternalRouteErrorMessage.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/createLocalAgentRunOptions.d.ts +9 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/forwardChildOutput.d.ts +13 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/loadAgentsServerProjectEnvironment.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/prepareAgentsServerLaunch.d.ts +18 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/readInternalRouteErrorDetails.d.ts +6 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/resolveAgentsServerChildHostname.d.ts +7 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/startNextServer.d.ts +20 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/startUserChatJobWorkerPump.d.ts +15 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/stopChildProcess.d.ts +8 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer/waitForLocalAgentRunnerLimits.d.ts +16 -0
- package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +3 -30
- package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +1 -1
- package/umd/src/collection/agent-collection/CreateAgentInput.d.ts +1 -1
- package/umd/src/collection/agent-collection/constructors/agent-collection-in-supabase/prepareAgentSourceForPersistence.d.ts +1 -1
- package/umd/src/commitments/META/META.d.ts +2 -2
- package/umd/src/commitments/META_AVATAR/META_AVATAR.d.ts +9 -4
- package/umd/src/commitments/index.d.ts +1 -1
- package/umd/src/conversion/validation/validatePipeline/createPipelineValidationContext.d.ts +16 -0
- package/umd/src/conversion/validation/validatePipeline/validatePipelineCollectionsStructure.d.ts +7 -0
- package/umd/src/conversion/validation/validatePipeline/validatePipelineDependencyResolution.d.ts +7 -0
- package/umd/src/conversion/validation/validatePipeline/validatePipelineMetadata.d.ts +7 -0
- package/umd/src/conversion/validation/validatePipeline/validatePipelineParameters.d.ts +7 -0
- package/umd/src/conversion/validation/validatePipeline/validatePipelineTasks.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/RegisteredLlmToolsMessageContext.d.ts +46 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/USED_ENV_FILENAME.d.ts +11 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolConfigurationStatusMessage.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolEntryKey.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolInstallationStatusMessage.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolStatus.d.ts +14 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/createRegisteredLlmToolsMessageContext.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/getAvailableRegisteredLlmToolsEnvironmentVariables.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/getRegisteredLlmToolRegisters.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/hasRegisteredLlmToolEntry.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/listRegisteredLlmToolEntries.d.ts +9 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/listRegisteredLlmToolStatuses.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderAvailableRegisteredLlmTools.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderNoRegisteredLlmToolsMessage.d.ts +7 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderRegisteredLlmToolsMessage.d.ts +8 -0
- package/umd/src/llm-providers/_common/register/$registeredLlmToolsMessage/renderRelevantRegisteredLlmToolsEnvironmentVariables.d.ts +7 -0
- package/umd/src/utils/agents/resolveAgentAvatarImageUrl.d.ts +2 -2
- package/umd/src/utils/color/Color.d.ts +1 -1
- package/umd/src/version.d.ts +1 -1
package/esm/index.es.js
CHANGED
|
@@ -29,7 +29,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
29
29
|
* @generated
|
|
30
30
|
* @see https://github.com/webgptorg/promptbook
|
|
31
31
|
*/
|
|
32
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.113.0-
|
|
32
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.113.0-9';
|
|
33
33
|
/**
|
|
34
34
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
35
35
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -92,6 +92,127 @@ function take(initialValue) {
|
|
|
92
92
|
});
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Validates that a channel value is a valid number within the range of 0 to 255.
|
|
97
|
+
* Throws an error if the value is not valid.
|
|
98
|
+
*
|
|
99
|
+
* @param channelName - The name of the channel being validated.
|
|
100
|
+
* @param value - The value of the channel to validate.
|
|
101
|
+
* @throws Will throw an error if the value is not a valid channel number.
|
|
102
|
+
*
|
|
103
|
+
* @private util of `@promptbook/color`
|
|
104
|
+
*/
|
|
105
|
+
function checkChannelValue(channelName, value) {
|
|
106
|
+
if (typeof value !== 'number') {
|
|
107
|
+
throw new Error(`${channelName} channel value is not number but ${typeof value}`);
|
|
108
|
+
}
|
|
109
|
+
if (isNaN(value)) {
|
|
110
|
+
throw new Error(`${channelName} channel value is NaN`);
|
|
111
|
+
}
|
|
112
|
+
if (Math.round(value) !== value) {
|
|
113
|
+
throw new Error(`${channelName} channel is not whole number, it is ${value}`);
|
|
114
|
+
}
|
|
115
|
+
if (value < 0) {
|
|
116
|
+
throw new Error(`${channelName} channel is lower than 0, it is ${value}`);
|
|
117
|
+
}
|
|
118
|
+
if (value > 255) {
|
|
119
|
+
throw new Error(`${channelName} channel is greater than 255, it is ${value}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Shared immutable channel storage and serialization helpers for `Color`.
|
|
125
|
+
*
|
|
126
|
+
* @private base class of Color
|
|
127
|
+
*/
|
|
128
|
+
class ColorValue {
|
|
129
|
+
constructor(red, green, blue, alpha = 255) {
|
|
130
|
+
this.red = red;
|
|
131
|
+
this.green = green;
|
|
132
|
+
this.blue = blue;
|
|
133
|
+
this.alpha = alpha;
|
|
134
|
+
checkChannelValue('Red', red);
|
|
135
|
+
checkChannelValue('Green', green);
|
|
136
|
+
checkChannelValue('Blue', blue);
|
|
137
|
+
checkChannelValue('Alpha', alpha);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Shortcut for `red` property
|
|
141
|
+
* Number from 0 to 255
|
|
142
|
+
* @alias red
|
|
143
|
+
*/
|
|
144
|
+
get r() {
|
|
145
|
+
return this.red;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Shortcut for `green` property
|
|
149
|
+
* Number from 0 to 255
|
|
150
|
+
* @alias green
|
|
151
|
+
*/
|
|
152
|
+
get g() {
|
|
153
|
+
return this.green;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Shortcut for `blue` property
|
|
157
|
+
* Number from 0 to 255
|
|
158
|
+
* @alias blue
|
|
159
|
+
*/
|
|
160
|
+
get b() {
|
|
161
|
+
return this.blue;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Shortcut for `alpha` property
|
|
165
|
+
* Number from 0 (transparent) to 255 (opaque)
|
|
166
|
+
* @alias alpha
|
|
167
|
+
*/
|
|
168
|
+
get a() {
|
|
169
|
+
return this.alpha;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Shortcut for `alpha` property
|
|
173
|
+
* Number from 0 (transparent) to 255 (opaque)
|
|
174
|
+
* @alias alpha
|
|
175
|
+
*/
|
|
176
|
+
get opacity() {
|
|
177
|
+
return this.alpha;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Shortcut for 1-`alpha` property
|
|
181
|
+
*/
|
|
182
|
+
get transparency() {
|
|
183
|
+
return 255 - this.alpha;
|
|
184
|
+
}
|
|
185
|
+
clone() {
|
|
186
|
+
return take(this.createColor(this.red, this.green, this.blue, this.alpha));
|
|
187
|
+
}
|
|
188
|
+
toString() {
|
|
189
|
+
return this.toHex();
|
|
190
|
+
}
|
|
191
|
+
toHex() {
|
|
192
|
+
if (this.alpha === 255) {
|
|
193
|
+
return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
|
|
194
|
+
.toString(16)
|
|
195
|
+
.padStart(2, '0')}`;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
|
|
199
|
+
.toString(16)
|
|
200
|
+
.padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
toRgb() {
|
|
204
|
+
if (this.alpha === 255) {
|
|
205
|
+
return `rgb(${this.red}, ${this.green}, ${this.blue})`;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
toHsl() {
|
|
212
|
+
throw new Error(`Getting HSL is not implemented`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
95
216
|
/**
|
|
96
217
|
* 🎨 List of all 140 color names which are supported by CSS
|
|
97
218
|
*
|
|
@@ -251,127 +372,6 @@ const CSS_COLORS = {
|
|
|
251
372
|
};
|
|
252
373
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
253
374
|
|
|
254
|
-
/**
|
|
255
|
-
* Validates that a channel value is a valid number within the range of 0 to 255.
|
|
256
|
-
* Throws an error if the value is not valid.
|
|
257
|
-
*
|
|
258
|
-
* @param channelName - The name of the channel being validated.
|
|
259
|
-
* @param value - The value of the channel to validate.
|
|
260
|
-
* @throws Will throw an error if the value is not a valid channel number.
|
|
261
|
-
*
|
|
262
|
-
* @private util of `@promptbook/color`
|
|
263
|
-
*/
|
|
264
|
-
function checkChannelValue(channelName, value) {
|
|
265
|
-
if (typeof value !== 'number') {
|
|
266
|
-
throw new Error(`${channelName} channel value is not number but ${typeof value}`);
|
|
267
|
-
}
|
|
268
|
-
if (isNaN(value)) {
|
|
269
|
-
throw new Error(`${channelName} channel value is NaN`);
|
|
270
|
-
}
|
|
271
|
-
if (Math.round(value) !== value) {
|
|
272
|
-
throw new Error(`${channelName} channel is not whole number, it is ${value}`);
|
|
273
|
-
}
|
|
274
|
-
if (value < 0) {
|
|
275
|
-
throw new Error(`${channelName} channel is lower than 0, it is ${value}`);
|
|
276
|
-
}
|
|
277
|
-
if (value > 255) {
|
|
278
|
-
throw new Error(`${channelName} channel is greater than 255, it is ${value}`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Shared immutable channel storage and serialization helpers for `Color`.
|
|
284
|
-
*
|
|
285
|
-
* @private base class of Color
|
|
286
|
-
*/
|
|
287
|
-
class ColorValue {
|
|
288
|
-
constructor(red, green, blue, alpha = 255) {
|
|
289
|
-
this.red = red;
|
|
290
|
-
this.green = green;
|
|
291
|
-
this.blue = blue;
|
|
292
|
-
this.alpha = alpha;
|
|
293
|
-
checkChannelValue('Red', red);
|
|
294
|
-
checkChannelValue('Green', green);
|
|
295
|
-
checkChannelValue('Blue', blue);
|
|
296
|
-
checkChannelValue('Alpha', alpha);
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Shortcut for `red` property
|
|
300
|
-
* Number from 0 to 255
|
|
301
|
-
* @alias red
|
|
302
|
-
*/
|
|
303
|
-
get r() {
|
|
304
|
-
return this.red;
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Shortcut for `green` property
|
|
308
|
-
* Number from 0 to 255
|
|
309
|
-
* @alias green
|
|
310
|
-
*/
|
|
311
|
-
get g() {
|
|
312
|
-
return this.green;
|
|
313
|
-
}
|
|
314
|
-
/**
|
|
315
|
-
* Shortcut for `blue` property
|
|
316
|
-
* Number from 0 to 255
|
|
317
|
-
* @alias blue
|
|
318
|
-
*/
|
|
319
|
-
get b() {
|
|
320
|
-
return this.blue;
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Shortcut for `alpha` property
|
|
324
|
-
* Number from 0 (transparent) to 255 (opaque)
|
|
325
|
-
* @alias alpha
|
|
326
|
-
*/
|
|
327
|
-
get a() {
|
|
328
|
-
return this.alpha;
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Shortcut for `alpha` property
|
|
332
|
-
* Number from 0 (transparent) to 255 (opaque)
|
|
333
|
-
* @alias alpha
|
|
334
|
-
*/
|
|
335
|
-
get opacity() {
|
|
336
|
-
return this.alpha;
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Shortcut for 1-`alpha` property
|
|
340
|
-
*/
|
|
341
|
-
get transparency() {
|
|
342
|
-
return 255 - this.alpha;
|
|
343
|
-
}
|
|
344
|
-
clone() {
|
|
345
|
-
return take(this.createColor(this.red, this.green, this.blue, this.alpha));
|
|
346
|
-
}
|
|
347
|
-
toString() {
|
|
348
|
-
return this.toHex();
|
|
349
|
-
}
|
|
350
|
-
toHex() {
|
|
351
|
-
if (this.alpha === 255) {
|
|
352
|
-
return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
|
|
353
|
-
.toString(16)
|
|
354
|
-
.padStart(2, '0')}`;
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
|
|
358
|
-
.toString(16)
|
|
359
|
-
.padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
toRgb() {
|
|
363
|
-
if (this.alpha === 255) {
|
|
364
|
-
return `rgb(${this.red}, ${this.green}, ${this.blue})`;
|
|
365
|
-
}
|
|
366
|
-
else {
|
|
367
|
-
return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
toHsl() {
|
|
371
|
-
throw new Error(`Getting HSL is not implemented`);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
375
|
/**
|
|
376
376
|
* Checks if the given value is a valid hex color string
|
|
377
377
|
*
|
|
@@ -4816,7 +4816,7 @@ class MessageSuffixCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
4816
4816
|
* META commitment definition
|
|
4817
4817
|
*
|
|
4818
4818
|
* The META commitment handles all meta-information about the agent such as:
|
|
4819
|
-
* - META AVATAR: Sets the agent's built-in default avatar visual
|
|
4819
|
+
* - META AVATAR / META VISUAL: Sets the agent's built-in default avatar visual
|
|
4820
4820
|
* - META IMAGE: Sets the agent's avatar/profile image URL
|
|
4821
4821
|
* - META LINK: Provides profile/source links for the person the agent models
|
|
4822
4822
|
* - META DOMAIN: Sets the canonical custom domain/host of the agent
|
|
@@ -4831,7 +4831,7 @@ class MessageSuffixCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
4831
4831
|
* Example usage in agent source:
|
|
4832
4832
|
*
|
|
4833
4833
|
* ```book
|
|
4834
|
-
* META
|
|
4834
|
+
* META VISUAL pixel-art
|
|
4835
4835
|
* META IMAGE https://example.com/avatar.jpg
|
|
4836
4836
|
* META LINK https://twitter.com/username
|
|
4837
4837
|
* META DOMAIN my-agent.com
|
|
@@ -4871,7 +4871,7 @@ class MetaCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
4871
4871
|
|
|
4872
4872
|
## Supported META types
|
|
4873
4873
|
|
|
4874
|
-
- **META AVATAR** - Sets the agent's built-in default avatar visual
|
|
4874
|
+
- **META AVATAR** / **META VISUAL** - Sets the agent's built-in default avatar visual
|
|
4875
4875
|
- **META IMAGE** - Sets the agent's avatar/profile image URL
|
|
4876
4876
|
- **META LINK** - Provides profile/source links for the person the agent models
|
|
4877
4877
|
- **META DOMAIN** - Sets the canonical custom domain/host of the agent
|
|
@@ -4895,7 +4895,7 @@ class MetaCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
4895
4895
|
\`\`\`book
|
|
4896
4896
|
Professional Assistant
|
|
4897
4897
|
|
|
4898
|
-
META
|
|
4898
|
+
META VISUAL octopus3
|
|
4899
4899
|
META IMAGE https://example.com/professional-avatar.jpg
|
|
4900
4900
|
META TITLE Senior Business Consultant
|
|
4901
4901
|
META DESCRIPTION Specialized in strategic planning and project management
|
|
@@ -8068,212 +8068,149 @@ function formatAlphaHex$1(opacity) {
|
|
|
8068
8068
|
|
|
8069
8069
|
/* eslint-disable no-magic-numbers */
|
|
8070
8070
|
/**
|
|
8071
|
-
* Light direction used by the
|
|
8071
|
+
* Light direction used by the single-mesh octopus shading.
|
|
8072
8072
|
*
|
|
8073
|
-
* @private helper of `
|
|
8073
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8074
8074
|
*/
|
|
8075
8075
|
const LIGHT_DIRECTION$3 = normalizeVector3({
|
|
8076
|
-
x: 0.
|
|
8077
|
-
y: -0.
|
|
8078
|
-
z: 0.
|
|
8076
|
+
x: 0.38,
|
|
8077
|
+
y: -0.6,
|
|
8078
|
+
z: 0.98,
|
|
8079
8079
|
});
|
|
8080
8080
|
/**
|
|
8081
8081
|
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
8082
8082
|
*
|
|
8083
|
-
* @private helper of `
|
|
8083
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8084
8084
|
*/
|
|
8085
|
-
const
|
|
8085
|
+
const octopus3d2StableStateCache = new WeakMap();
|
|
8086
8086
|
/**
|
|
8087
8087
|
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
8088
8088
|
*
|
|
8089
|
-
* @private helper of `
|
|
8089
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8090
8090
|
*/
|
|
8091
|
-
function
|
|
8092
|
-
const cached =
|
|
8091
|
+
function getOctopus3d2StableState(createRandom) {
|
|
8092
|
+
const cached = octopus3d2StableStateCache.get(createRandom);
|
|
8093
8093
|
if (cached !== undefined) {
|
|
8094
8094
|
return cached;
|
|
8095
8095
|
}
|
|
8096
|
-
const animationRandom = createRandom('
|
|
8097
|
-
const eyeRandom = createRandom('
|
|
8098
|
-
const leftEyePhaseOffset = eyeRandom() * 0.
|
|
8099
|
-
const rightEyePhaseOffset = eyeRandom() * 0.
|
|
8096
|
+
const animationRandom = createRandom('octopus3d2-animation-profile');
|
|
8097
|
+
const eyeRandom = createRandom('octopus3d2-eye-profile');
|
|
8098
|
+
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
8099
|
+
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
8100
8100
|
const state = {
|
|
8101
8101
|
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
8102
8102
|
animationPhase: animationRandom() * Math.PI * 2,
|
|
8103
8103
|
leftEyePhaseOffset,
|
|
8104
8104
|
rightEyePhaseOffset,
|
|
8105
8105
|
};
|
|
8106
|
-
|
|
8106
|
+
octopus3d2StableStateCache.set(createRandom, state);
|
|
8107
8107
|
return state;
|
|
8108
8108
|
}
|
|
8109
8109
|
/**
|
|
8110
|
-
*
|
|
8110
|
+
* Octopus 3D 2 avatar visual.
|
|
8111
8111
|
*
|
|
8112
8112
|
* @private built-in avatar visual
|
|
8113
8113
|
*/
|
|
8114
|
-
const
|
|
8115
|
-
id: '
|
|
8116
|
-
title: 'Octopus 3D',
|
|
8117
|
-
description: '
|
|
8114
|
+
const octopus3d2AvatarVisual = {
|
|
8115
|
+
id: 'octopus3d2',
|
|
8116
|
+
title: 'Octopus 3D 2',
|
|
8117
|
+
description: 'Continuous blobby 3D octopus portrait with one soft mesh, turning silhouette, and cursor-aware eyes.',
|
|
8118
8118
|
isAnimated: true,
|
|
8119
8119
|
supportsPointerTracking: true,
|
|
8120
8120
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
8121
|
-
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } =
|
|
8121
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3d2StableState(createRandom);
|
|
8122
8122
|
const sceneCenterX = size * 0.5;
|
|
8123
|
-
const sceneCenterY = size * 0.
|
|
8124
|
-
const bob = Math.sin(timeMs /
|
|
8125
|
-
const
|
|
8126
|
-
x: interaction.bodyOffsetX * size * 0.
|
|
8127
|
-
y: -size * 0.
|
|
8128
|
-
z: interaction.intensity * size * 0.
|
|
8129
|
-
};
|
|
8130
|
-
const underbodyCenter = {
|
|
8131
|
-
x: mantleCenter.x * 0.86,
|
|
8132
|
-
y: mantleCenter.y + size * 0.168,
|
|
8133
|
-
z: mantleCenter.z - size * 0.018,
|
|
8123
|
+
const sceneCenterY = size * 0.575;
|
|
8124
|
+
const bob = Math.sin(timeMs / 940 + animationPhase) * size * 0.013;
|
|
8125
|
+
const meshCenter = {
|
|
8126
|
+
x: interaction.bodyOffsetX * size * 0.044 + size * morphologyProfile.body.centerXJitterRatio * 0.5,
|
|
8127
|
+
y: -size * 0.03 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
8128
|
+
z: interaction.intensity * size * 0.018,
|
|
8134
8129
|
};
|
|
8135
|
-
const
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
const underbodyRadiusY = mantleRadiusY * (0.44 + morphologyProfile.body.lowerDropRatio * 3.1);
|
|
8142
|
-
const underbodyRadiusZ = mantleRadiusZ * 0.78;
|
|
8143
|
-
const bodyYaw = -0.18 +
|
|
8144
|
-
Math.sin(timeMs / 2400 + animationPhase) * 0.05 +
|
|
8145
|
-
interaction.bodyOffsetX * 0.18 +
|
|
8146
|
-
interaction.gazeX * 0.22;
|
|
8147
|
-
const bodyPitch = -0.08 +
|
|
8148
|
-
Math.cos(timeMs / 2700 + animationPhase * 0.6) * 0.025 -
|
|
8130
|
+
const rotationY = -0.14 +
|
|
8131
|
+
Math.sin(timeMs / 2600 + animationPhase) * 0.04 +
|
|
8132
|
+
interaction.bodyOffsetX * 0.2 +
|
|
8133
|
+
interaction.gazeX * 0.78;
|
|
8134
|
+
const rotationX = -0.06 +
|
|
8135
|
+
Math.cos(timeMs / 3000 + animationPhase * 0.7) * 0.02 -
|
|
8149
8136
|
interaction.bodyOffsetY * 0.08 -
|
|
8150
|
-
interaction.gazeY * 0.
|
|
8151
|
-
const
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
radiusZ: mantleRadiusZ,
|
|
8158
|
-
rotationX: headPitch,
|
|
8159
|
-
rotationY: headYaw,
|
|
8160
|
-
sceneCenterX,
|
|
8161
|
-
sceneCenterY,
|
|
8162
|
-
size,
|
|
8163
|
-
palette,
|
|
8164
|
-
verticalColorBias: 0,
|
|
8165
|
-
outlineColor: `${palette.highlight}7a`,
|
|
8166
|
-
});
|
|
8167
|
-
const underbodyPatches = resolveVisibleEllipsoidPatches({
|
|
8168
|
-
center: underbodyCenter,
|
|
8169
|
-
radiusX: underbodyRadiusX,
|
|
8170
|
-
radiusY: underbodyRadiusY,
|
|
8171
|
-
radiusZ: underbodyRadiusZ,
|
|
8172
|
-
rotationX: bodyPitch,
|
|
8173
|
-
rotationY: bodyYaw,
|
|
8174
|
-
sceneCenterX,
|
|
8175
|
-
sceneCenterY,
|
|
8176
|
-
size,
|
|
8177
|
-
palette,
|
|
8178
|
-
verticalColorBias: 0.18,
|
|
8179
|
-
outlineColor: `${palette.shadow}8f`,
|
|
8180
|
-
});
|
|
8181
|
-
const tentacleStrokes = createOctopusTentacleStrokes({
|
|
8182
|
-
createRandom,
|
|
8137
|
+
interaction.gazeY * 0.34;
|
|
8138
|
+
const surfaceOptions = {
|
|
8139
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.02,
|
|
8140
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.22,
|
|
8141
|
+
radiusZ: size *
|
|
8142
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
8143
|
+
(0.98 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
8183
8144
|
morphologyProfile,
|
|
8184
8145
|
timeMs,
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
rotationX
|
|
8191
|
-
rotationY
|
|
8146
|
+
animationPhase,
|
|
8147
|
+
};
|
|
8148
|
+
const surfacePatches = resolveVisibleBlobbyOctopusPatches({
|
|
8149
|
+
...surfaceOptions,
|
|
8150
|
+
center: meshCenter,
|
|
8151
|
+
rotationX,
|
|
8152
|
+
rotationY,
|
|
8192
8153
|
sceneCenterX,
|
|
8193
8154
|
sceneCenterY,
|
|
8194
|
-
|
|
8155
|
+
size,
|
|
8156
|
+
palette,
|
|
8195
8157
|
});
|
|
8196
|
-
const
|
|
8197
|
-
const
|
|
8198
|
-
const
|
|
8199
|
-
const
|
|
8200
|
-
const
|
|
8201
|
-
const
|
|
8158
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4, -0.16, 0.11);
|
|
8159
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.25, 0.2, 0.34);
|
|
8160
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.19 + morphologyProfile.face.mouthYOffsetRatio * 1.08, 0.08, 0.34);
|
|
8161
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.8, -0.08, 0.08);
|
|
8162
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.82, 0.16, 0.29);
|
|
8163
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.85, mouthLatitude + 0.03, 0.42);
|
|
8202
8164
|
drawAvatarFrame(context, size, palette);
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
for (const
|
|
8206
|
-
|
|
8207
|
-
}
|
|
8208
|
-
for (const surfacePatch of [...mantlePatches, ...underbodyPatches].sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
8209
|
-
drawSurfacePatch(context, surfacePatch);
|
|
8210
|
-
}
|
|
8211
|
-
for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
|
|
8212
|
-
drawTentacleStroke(context, tentacleStroke, palette);
|
|
8165
|
+
drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
8166
|
+
drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
8167
|
+
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
8168
|
+
drawBlobbySurfacePatch(context, surfacePatch);
|
|
8213
8169
|
}
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
drawProjectedOrganicEye(context,
|
|
8220
|
-
x: faceEyeSpacing,
|
|
8221
|
-
y: faceEyeYOffset,
|
|
8222
|
-
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
|
|
8223
|
-
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8170
|
+
const leftEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude);
|
|
8171
|
+
const rightEyeLocalCenter = sampleBlobbyOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude);
|
|
8172
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
8173
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
8174
|
+
drawProjectedOrganicEye(context, leftEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8175
|
+
drawProjectedOrganicEye(context, rightEyeLocalCenter, eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.9 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8224
8176
|
drawProjectedOrganicMouth(context, [
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
},
|
|
8230
|
-
{
|
|
8231
|
-
x: size * morphologyProfile.face.mouthCenterOffsetRatio,
|
|
8232
|
-
y: mouthY +
|
|
8233
|
-
size * morphologyProfile.face.mouthCurveDepthRatio * 0.38 +
|
|
8234
|
-
Math.sin(timeMs / 760 + animationPhase) * size * 0.01 +
|
|
8235
|
-
interaction.gazeY * size * 0.01,
|
|
8236
|
-
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, size * morphologyProfile.face.mouthCenterOffsetRatio, mouthY),
|
|
8237
|
-
},
|
|
8238
|
-
{
|
|
8239
|
-
x: mouthHalfWidth,
|
|
8240
|
-
y: mouthY,
|
|
8241
|
-
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY),
|
|
8242
|
-
},
|
|
8243
|
-
], mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, palette, size);
|
|
8177
|
+
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
8178
|
+
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
8179
|
+
sampleBlobbyOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
8180
|
+
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
8244
8181
|
},
|
|
8245
8182
|
};
|
|
8246
8183
|
/**
|
|
8247
|
-
* Draws the
|
|
8184
|
+
* Draws the deep-water glow behind the continuous octopus mesh.
|
|
8248
8185
|
*
|
|
8249
|
-
* @private helper of `
|
|
8186
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8250
8187
|
*/
|
|
8251
|
-
function
|
|
8252
|
-
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.
|
|
8253
|
-
glowGradient.addColorStop(0, `${palette.highlight}
|
|
8254
|
-
glowGradient.addColorStop(0.
|
|
8188
|
+
function drawBlobbyOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
8189
|
+
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.11, sceneCenterY - size * 0.17 + interaction.gazeY * size * 0.05, size * 0.05, sceneCenterX, sceneCenterY - size * 0.03, size * 0.66);
|
|
8190
|
+
glowGradient.addColorStop(0, `${palette.highlight}5e`);
|
|
8191
|
+
glowGradient.addColorStop(0.38, `${palette.accent}26`);
|
|
8255
8192
|
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
8256
8193
|
context.fillStyle = glowGradient;
|
|
8257
8194
|
context.fillRect(0, 0, size, size);
|
|
8258
|
-
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs /
|
|
8259
|
-
lowerGradient.addColorStop(0, `${palette.secondary}
|
|
8195
|
+
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1650) * size * 0.045, sceneCenterY + size * 0.28, size * 0.06, sceneCenterX, sceneCenterY + size * 0.28, size * 0.52);
|
|
8196
|
+
lowerGradient.addColorStop(0, `${palette.secondary}22`);
|
|
8260
8197
|
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
8261
8198
|
context.fillStyle = lowerGradient;
|
|
8262
8199
|
context.fillRect(0, 0, size, size);
|
|
8263
8200
|
}
|
|
8264
8201
|
/**
|
|
8265
|
-
* Draws the soft
|
|
8202
|
+
* Draws the soft floor shadow that anchors the single mesh in the frame.
|
|
8266
8203
|
*
|
|
8267
8204
|
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
8268
8205
|
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
8269
8206
|
*
|
|
8270
|
-
* @private helper of `
|
|
8207
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8271
8208
|
*/
|
|
8272
|
-
function
|
|
8273
|
-
const cx = size * 0.5 + interaction.gazeX * size * 0.
|
|
8274
|
-
const cy = size * 0.
|
|
8275
|
-
const rx = size * (0.18 + interaction.intensity * 0.
|
|
8276
|
-
const ry = size * 0.
|
|
8209
|
+
function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
8210
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
8211
|
+
const cy = size * 0.88 + Math.sin(timeMs / 940) * size * 0.008;
|
|
8212
|
+
const rx = size * (0.18 + (morphologyProfile.body.horizontalStretch - 1) * 0.04 + interaction.intensity * 0.018);
|
|
8213
|
+
const ry = size * 0.062;
|
|
8277
8214
|
context.save();
|
|
8278
8215
|
context.translate(cx, cy);
|
|
8279
8216
|
context.scale(1, ry / rx);
|
|
@@ -8290,86 +8227,181 @@ function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
|
|
|
8290
8227
|
context.restore();
|
|
8291
8228
|
}
|
|
8292
8229
|
/**
|
|
8293
|
-
*
|
|
8230
|
+
* Number of latitude segments used by the single blobby octopus mesh.
|
|
8294
8231
|
*
|
|
8295
|
-
* @private helper of `
|
|
8232
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8296
8233
|
*/
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8234
|
+
const LATITUDE_PATCH_COUNT$2 = 12;
|
|
8235
|
+
/**
|
|
8236
|
+
* Number of longitude segments used by the single blobby octopus mesh.
|
|
8237
|
+
*
|
|
8238
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8239
|
+
*/
|
|
8240
|
+
const LONGITUDE_PATCH_COUNT$2 = 24;
|
|
8241
|
+
/**
|
|
8242
|
+
* Resolves all visible projected patches for the single blobby octopus mesh.
|
|
8243
|
+
*
|
|
8244
|
+
* Within a single frame, mesh corner samples and longitude-only lobe-wave values are
|
|
8245
|
+
* quantized to the patch grid and computed once each rather than re-evaluated for every
|
|
8246
|
+
* patch corner — the patch loop alone would call `sampleBlobbyOctopusSurfacePoint`
|
|
8247
|
+
* `latitudePatchCount * longitudePatchCount * 4` times without caching, even though most
|
|
8248
|
+
* corners are shared between neighboring patches.
|
|
8249
|
+
*
|
|
8250
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8251
|
+
*/
|
|
8252
|
+
function resolveVisibleBlobbyOctopusPatches(options) {
|
|
8253
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, animationPhase, timeMs, } = options;
|
|
8254
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$2;
|
|
8255
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$2;
|
|
8301
8256
|
const surfacePatches = [];
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8257
|
+
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
8258
|
+
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
8259
|
+
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
8260
|
+
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
8261
|
+
}
|
|
8262
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
8263
|
+
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
8264
|
+
}
|
|
8265
|
+
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
8266
|
+
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
8267
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveLowerLobeWave(longitudeBoundaries[boundaryIndex], morphologyProfile, animationPhase, timeMs);
|
|
8268
|
+
}
|
|
8269
|
+
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
8270
|
+
const transformedCornerSamples = new Array(cornerCount);
|
|
8271
|
+
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
8272
|
+
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
8273
|
+
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
8274
|
+
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
8275
|
+
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
8276
|
+
const cornerSample = sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
8277
|
+
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
8278
|
+
}
|
|
8279
|
+
}
|
|
8280
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
8281
|
+
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
8282
|
+
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
8283
|
+
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
8284
|
+
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
8285
|
+
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
8286
|
+
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
8287
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
8288
|
+
const startLongitude = longitudeBoundaries[longitudeIndex];
|
|
8289
|
+
const endLongitude = longitudeBoundaries[longitudeIndex + 1];
|
|
8290
|
+
const centerLongitude = (startLongitude + endLongitude) / 2;
|
|
8291
|
+
const transformedCorners = [
|
|
8292
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
8293
|
+
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
8294
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
8295
|
+
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
8314
8296
|
];
|
|
8315
|
-
const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
|
|
8316
8297
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
8317
8298
|
if (surfaceNormal.z <= 0.01) {
|
|
8318
8299
|
continue;
|
|
8319
8300
|
}
|
|
8320
|
-
const projectedCorners =
|
|
8301
|
+
const projectedCorners = [
|
|
8302
|
+
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
8303
|
+
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
8304
|
+
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
8305
|
+
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
8306
|
+
];
|
|
8321
8307
|
surfacePatches.push({
|
|
8322
8308
|
corners: projectedCorners,
|
|
8323
|
-
averageDepth: transformedCorners.
|
|
8324
|
-
transformedCorners.
|
|
8309
|
+
averageDepth: (transformedCorners[0].z +
|
|
8310
|
+
transformedCorners[1].z +
|
|
8311
|
+
transformedCorners[2].z +
|
|
8312
|
+
transformedCorners[3].z) /
|
|
8313
|
+
4,
|
|
8325
8314
|
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$3), -1, 1),
|
|
8326
|
-
fillStyle:
|
|
8327
|
-
outlineColor
|
|
8315
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, Math.max(0, Math.cos(centerLongitude)), resolveLowerLobeWave(centerLongitude, morphologyProfile, animationPhase, timeMs)),
|
|
8316
|
+
outlineColor: verticalProgress < 0.58 ? `${palette.highlight}73` : `${palette.shadow}8a`,
|
|
8328
8317
|
});
|
|
8329
8318
|
}
|
|
8330
8319
|
}
|
|
8331
8320
|
return surfacePatches;
|
|
8332
8321
|
}
|
|
8333
8322
|
/**
|
|
8334
|
-
* Samples one point on
|
|
8323
|
+
* Samples one point on the continuous Octopus 3D 2 surface.
|
|
8335
8324
|
*
|
|
8336
|
-
*
|
|
8325
|
+
* The lower hemisphere widens and falls into soft lobe waves so the octopus stays one connected mesh
|
|
8326
|
+
* instead of switching to separately rendered tentacles.
|
|
8327
|
+
*
|
|
8328
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8337
8329
|
*/
|
|
8338
|
-
function
|
|
8339
|
-
const
|
|
8330
|
+
function sampleBlobbyOctopusSurfacePoint(options, latitude, longitude) {
|
|
8331
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
8332
|
+
return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
|
|
8333
|
+
}
|
|
8334
|
+
/**
|
|
8335
|
+
* Samples one point on the continuous Octopus 3D 2 surface using a precomputed lower-lobe wave
|
|
8336
|
+
* to skip the per-call trig evaluation for `latitudePatchCount + 1` longitude-shared corners.
|
|
8337
|
+
*
|
|
8338
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8339
|
+
*/
|
|
8340
|
+
function sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, lowerLobeWave) {
|
|
8341
|
+
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
8342
|
+
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
8343
|
+
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
8344
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.2);
|
|
8345
|
+
const lowerBlend = Math.pow(verticalProgress, 1.42);
|
|
8346
|
+
const skirtEnvelope = Math.pow(cosineLatitude, 0.5) * lowerBlend;
|
|
8347
|
+
const horizontalScale = 1.02 +
|
|
8348
|
+
skirtEnvelope * (0.34 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.22 + lowerLobeWave * 0.22) -
|
|
8349
|
+
upperBlend * 0.08;
|
|
8350
|
+
const depthScale = 1.04 +
|
|
8351
|
+
upperBlend * 0.16 +
|
|
8352
|
+
Math.max(0, Math.cos(longitude)) * 0.1 +
|
|
8353
|
+
skirtEnvelope * (0.08 + lowerLobeWave * 0.06) -
|
|
8354
|
+
Math.max(0, -Math.cos(longitude)) * 0.04;
|
|
8355
|
+
const lowerDrop = skirtEnvelope *
|
|
8356
|
+
radiusY *
|
|
8357
|
+
(0.28 + lowerLobeWave * 0.14 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.12);
|
|
8358
|
+
const swayX = Math.sin(timeMs / 1250 + longitude * 1.8 + animationPhase) * skirtEnvelope * radiusX * 0.05;
|
|
8359
|
+
const swayZ = Math.cos(timeMs / 1480 + longitude * 1.2 - animationPhase * 0.7) * skirtEnvelope * radiusZ * 0.03;
|
|
8340
8360
|
return {
|
|
8341
|
-
x: Math.sin(longitude) * cosineLatitude * radiusX,
|
|
8342
|
-
y: Math.sin(latitude) * radiusY
|
|
8343
|
-
|
|
8361
|
+
x: Math.sin(longitude) * cosineLatitude * radiusX * horizontalScale + swayX,
|
|
8362
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
8363
|
+
upperBlend * radiusY * 0.1 +
|
|
8364
|
+
lowerDrop +
|
|
8365
|
+
Math.sin(timeMs / 1780 + animationPhase + latitude * 1.4) * skirtEnvelope * radiusY * 0.02,
|
|
8366
|
+
z: Math.cos(longitude) * cosineLatitude * radiusZ * depthScale + swayZ,
|
|
8344
8367
|
};
|
|
8345
8368
|
}
|
|
8346
8369
|
/**
|
|
8347
|
-
* Resolves
|
|
8370
|
+
* Resolves the soft lower-lobe wave that makes the silhouette read more like a real octopus.
|
|
8348
8371
|
*
|
|
8349
|
-
* @private helper of `
|
|
8372
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8350
8373
|
*/
|
|
8351
|
-
function
|
|
8352
|
-
const
|
|
8353
|
-
|
|
8374
|
+
function resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs) {
|
|
8375
|
+
const lobeCount = Math.max(4, Math.round((morphologyProfile.body.lobeCount + morphologyProfile.tentacles.count) / 2));
|
|
8376
|
+
return (Math.cos(longitude * lobeCount + animationPhase + timeMs / 1040) + 1) / 2;
|
|
8377
|
+
}
|
|
8378
|
+
/**
|
|
8379
|
+
* Resolves one base fill tone for a surface patch on the single octopus mesh.
|
|
8380
|
+
*
|
|
8381
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8382
|
+
*/
|
|
8383
|
+
function resolveBlobbySurfacePatchFillStyle$1(palette, verticalProgress, forwardness, lowerLobeWave) {
|
|
8384
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.12 - forwardness * 0.07, 0, 1);
|
|
8385
|
+
if (tonalProgress < 0.16) {
|
|
8354
8386
|
return palette.highlight;
|
|
8355
8387
|
}
|
|
8356
|
-
if (
|
|
8388
|
+
if (tonalProgress < 0.34) {
|
|
8357
8389
|
return palette.secondary;
|
|
8358
8390
|
}
|
|
8359
|
-
if (
|
|
8360
|
-
return palette.primary;
|
|
8391
|
+
if (tonalProgress < 0.72) {
|
|
8392
|
+
return forwardness > 0.58 ? palette.secondary : palette.primary;
|
|
8361
8393
|
}
|
|
8362
8394
|
return `${palette.shadow}f2`;
|
|
8363
8395
|
}
|
|
8364
8396
|
/**
|
|
8365
|
-
* Draws one projected
|
|
8397
|
+
* Draws one projected patch with soft octopus shading.
|
|
8366
8398
|
*
|
|
8367
|
-
* @private helper of `
|
|
8399
|
+
* @private helper of `octopus3d2AvatarVisual`
|
|
8368
8400
|
*/
|
|
8369
|
-
function
|
|
8401
|
+
function drawBlobbySurfacePatch(context, surfacePatch) {
|
|
8370
8402
|
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
8371
8403
|
if (surfacePatch.lightIntensity > 0) {
|
|
8372
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.
|
|
8404
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.16 * surfacePatch.lightIntensity})`);
|
|
8373
8405
|
}
|
|
8374
8406
|
else if (surfacePatch.lightIntensity < 0) {
|
|
8375
8407
|
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
@@ -8382,209 +8414,104 @@ function drawSurfacePatch(context, surfacePatch) {
|
|
|
8382
8414
|
}
|
|
8383
8415
|
context.closePath();
|
|
8384
8416
|
context.strokeStyle = surfacePatch.outlineColor;
|
|
8385
|
-
context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.
|
|
8417
|
+
context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0042);
|
|
8386
8418
|
context.lineJoin = 'round';
|
|
8387
8419
|
context.stroke();
|
|
8388
8420
|
context.restore();
|
|
8389
8421
|
}
|
|
8422
|
+
|
|
8423
|
+
/* eslint-disable no-magic-numbers */
|
|
8390
8424
|
/**
|
|
8391
|
-
*
|
|
8425
|
+
* Light direction used by the continuous octopus mesh shading.
|
|
8392
8426
|
*
|
|
8393
|
-
* @private helper of `
|
|
8427
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8394
8428
|
*/
|
|
8395
|
-
|
|
8396
|
-
|
|
8397
|
-
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
const orbitAngle = -Math.PI * 0.92 + spreadProgress * Math.PI * 1.84 + (tentacleRandom() - 0.5) * 0.16;
|
|
8401
|
-
const flowLength = size * (0.19 + morphologyProfile.tentacles.flowLengthScale * 0.075 + tentacleRandom() * 0.018);
|
|
8402
|
-
const lateralReach = size *
|
|
8403
|
-
(0.08 + morphologyProfile.tentacles.lateralReachScale * 0.05 + Math.abs(Math.sin(orbitAngle)) * 0.018);
|
|
8404
|
-
const depthReach = size * (0.028 + morphologyProfile.tentacles.tipReachScale * 0.032);
|
|
8405
|
-
const sway = Math.sin(timeMs / (760 + tentacleIndex * 36) + animationPhase + tentacleRandom() * Math.PI * 2);
|
|
8406
|
-
const anchorPoint = {
|
|
8407
|
-
x: Math.sin(orbitAngle) * radiusX * (0.84 + tentacleRandom() * 0.08),
|
|
8408
|
-
y: radiusY * (0.22 + tentacleRandom() * 0.18),
|
|
8409
|
-
z: Math.cos(orbitAngle) * radiusZ * (0.72 + tentacleRandom() * 0.12),
|
|
8410
|
-
};
|
|
8411
|
-
const controlPointOne = {
|
|
8412
|
-
x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * 0.44,
|
|
8413
|
-
y: anchorPoint.y + flowLength * 0.26,
|
|
8414
|
-
z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.3 + sway * size * 0.012,
|
|
8415
|
-
};
|
|
8416
|
-
const controlPointTwo = {
|
|
8417
|
-
x: anchorPoint.x +
|
|
8418
|
-
Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
|
|
8419
|
-
y: anchorPoint.y + flowLength * 0.66,
|
|
8420
|
-
z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.72 + sway * size * 0.02,
|
|
8421
|
-
};
|
|
8422
|
-
const endPoint = {
|
|
8423
|
-
x: anchorPoint.x +
|
|
8424
|
-
Math.sin(orbitAngle) * lateralReach * (1.02 + morphologyProfile.tentacles.tipWidthScale * 0.12) +
|
|
8425
|
-
sway * size * 0.028,
|
|
8426
|
-
y: anchorPoint.y + flowLength,
|
|
8427
|
-
z: anchorPoint.z + Math.cos(orbitAngle) * depthReach + sway * size * 0.016,
|
|
8428
|
-
};
|
|
8429
|
-
const scenePoints = Array.from({ length: 12 }, (_, sampleIndex) => transformScenePoint(sampleCubicBezierPoint3D(anchorPoint, controlPointOne, controlPointTwo, endPoint, sampleIndex / 11), center, rotationX, rotationY));
|
|
8430
|
-
const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
8431
|
-
const averageDepth = scenePoints.reduce((depthSum, scenePoint) => depthSum + scenePoint.z, 0) / scenePoints.length;
|
|
8432
|
-
return {
|
|
8433
|
-
projectedPoints,
|
|
8434
|
-
averageDepth,
|
|
8435
|
-
isFrontFacing: averageDepth >= center.z - size * 0.006,
|
|
8436
|
-
baseWidth: size *
|
|
8437
|
-
(0.019 +
|
|
8438
|
-
morphologyProfile.tentacles.baseWidthScale * 0.007 +
|
|
8439
|
-
tentacleRandom() * 0.003 +
|
|
8440
|
-
Math.abs(Math.sin(orbitAngle)) * 0.002),
|
|
8441
|
-
tipWidth: size * (0.0046 + morphologyProfile.tentacles.tipWidthScale * 0.0018),
|
|
8442
|
-
colorBias: tentacleRandom(),
|
|
8443
|
-
};
|
|
8444
|
-
});
|
|
8445
|
-
}
|
|
8429
|
+
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
8430
|
+
x: 0.34,
|
|
8431
|
+
y: -0.62,
|
|
8432
|
+
z: 1,
|
|
8433
|
+
});
|
|
8446
8434
|
/**
|
|
8447
|
-
*
|
|
8435
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
8448
8436
|
*
|
|
8449
|
-
* @private helper of `
|
|
8437
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8450
8438
|
*/
|
|
8451
|
-
|
|
8452
|
-
const inverseProgress = 1 - progress;
|
|
8453
|
-
return {
|
|
8454
|
-
x: inverseProgress * inverseProgress * inverseProgress * startPoint.x +
|
|
8455
|
-
3 * inverseProgress * inverseProgress * progress * controlPointOne.x +
|
|
8456
|
-
3 * inverseProgress * progress * progress * controlPointTwo.x +
|
|
8457
|
-
progress * progress * progress * endPoint.x,
|
|
8458
|
-
y: inverseProgress * inverseProgress * inverseProgress * startPoint.y +
|
|
8459
|
-
3 * inverseProgress * inverseProgress * progress * controlPointOne.y +
|
|
8460
|
-
3 * inverseProgress * progress * progress * controlPointTwo.y +
|
|
8461
|
-
progress * progress * progress * endPoint.y,
|
|
8462
|
-
z: inverseProgress * inverseProgress * inverseProgress * startPoint.z +
|
|
8463
|
-
3 * inverseProgress * inverseProgress * progress * controlPointOne.z +
|
|
8464
|
-
3 * inverseProgress * progress * progress * controlPointTwo.z +
|
|
8465
|
-
progress * progress * progress * endPoint.z,
|
|
8466
|
-
};
|
|
8467
|
-
}
|
|
8439
|
+
const OCTOPUS_TENTACLE_COUNT$1 = 8;
|
|
8468
8440
|
/**
|
|
8469
|
-
*
|
|
8441
|
+
* Cache keyed by the `createRandom` factory reference, which is stable for the lifetime of one
|
|
8442
|
+
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
8443
|
+
* React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
|
|
8470
8444
|
*
|
|
8471
|
-
* @private helper of `
|
|
8445
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8472
8446
|
*/
|
|
8473
|
-
|
|
8474
|
-
const projectedSegments = tentacleStroke.projectedPoints.length - 1;
|
|
8475
|
-
for (let segmentIndex = 0; segmentIndex < projectedSegments; segmentIndex++) {
|
|
8476
|
-
const startPoint = tentacleStroke.projectedPoints[segmentIndex];
|
|
8477
|
-
const endPoint = tentacleStroke.projectedPoints[segmentIndex + 1];
|
|
8478
|
-
const progress = segmentIndex / projectedSegments;
|
|
8479
|
-
const width = tentacleStroke.baseWidth + (tentacleStroke.tipWidth - tentacleStroke.baseWidth) * progress;
|
|
8480
|
-
context.beginPath();
|
|
8481
|
-
context.moveTo(startPoint.x, startPoint.y);
|
|
8482
|
-
context.lineTo(endPoint.x, endPoint.y);
|
|
8483
|
-
context.strokeStyle = tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
|
|
8484
|
-
context.lineWidth = width;
|
|
8485
|
-
context.lineCap = 'round';
|
|
8486
|
-
context.stroke();
|
|
8487
|
-
context.beginPath();
|
|
8488
|
-
context.moveTo(startPoint.x, startPoint.y);
|
|
8489
|
-
context.lineTo(endPoint.x, endPoint.y);
|
|
8490
|
-
context.strokeStyle = tentacleStroke.isFrontFacing ? `${palette.highlight}80` : `${palette.highlight}40`;
|
|
8491
|
-
context.lineWidth = Math.max(1, width * 0.34);
|
|
8492
|
-
context.lineCap = 'round';
|
|
8493
|
-
context.stroke();
|
|
8494
|
-
}
|
|
8495
|
-
}
|
|
8496
|
-
/**
|
|
8497
|
-
* Resolves the front surface depth on an ellipsoid for one local face point.
|
|
8498
|
-
*
|
|
8499
|
-
* @private helper of `octopus3dAvatarVisual`
|
|
8500
|
-
*/
|
|
8501
|
-
function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
|
|
8502
|
-
const normalizedX = x / radiusX;
|
|
8503
|
-
const normalizedY = y / radiusY;
|
|
8504
|
-
const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
|
|
8505
|
-
return Math.sqrt(remainingDepthRatio) * radiusZ;
|
|
8506
|
-
}
|
|
8507
|
-
|
|
8508
|
-
/* eslint-disable no-magic-numbers */
|
|
8509
|
-
/**
|
|
8510
|
-
* Light direction used by the single-mesh octopus shading.
|
|
8511
|
-
*
|
|
8512
|
-
* @private helper of `octopus3d2AvatarVisual`
|
|
8513
|
-
*/
|
|
8514
|
-
const LIGHT_DIRECTION$2 = normalizeVector3({
|
|
8515
|
-
x: 0.38,
|
|
8516
|
-
y: -0.6,
|
|
8517
|
-
z: 0.98,
|
|
8518
|
-
});
|
|
8519
|
-
/**
|
|
8520
|
-
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
8521
|
-
*
|
|
8522
|
-
* @private helper of `octopus3d2AvatarVisual`
|
|
8523
|
-
*/
|
|
8524
|
-
const octopus3d2StableStateCache = new WeakMap();
|
|
8447
|
+
const stableStateCache = new WeakMap();
|
|
8525
8448
|
/**
|
|
8526
|
-
* Returns the stable per-avatar state, computing it on first access and
|
|
8449
|
+
* Returns the stable per-avatar state, computing it on first access and returning the cached
|
|
8450
|
+
* result on every subsequent call within the same `<Avatar/>` mount.
|
|
8527
8451
|
*
|
|
8528
|
-
* @private helper of `
|
|
8452
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8529
8453
|
*/
|
|
8530
|
-
function
|
|
8531
|
-
const cached =
|
|
8454
|
+
function getOctopus3d3StableState(createRandom) {
|
|
8455
|
+
const cached = stableStateCache.get(createRandom);
|
|
8532
8456
|
if (cached !== undefined) {
|
|
8533
8457
|
return cached;
|
|
8534
8458
|
}
|
|
8535
|
-
const
|
|
8536
|
-
const
|
|
8459
|
+
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
8460
|
+
const animationRandom = createRandom('octopus3d3-animation-profile');
|
|
8461
|
+
const eyeRandom = createRandom('octopus3d3-eye-profile');
|
|
8537
8462
|
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
8538
8463
|
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
8539
8464
|
const state = {
|
|
8540
|
-
morphologyProfile
|
|
8465
|
+
morphologyProfile,
|
|
8541
8466
|
animationPhase: animationRandom() * Math.PI * 2,
|
|
8542
8467
|
leftEyePhaseOffset,
|
|
8543
8468
|
rightEyePhaseOffset,
|
|
8469
|
+
tentacleProfiles: createContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
8544
8470
|
};
|
|
8545
|
-
|
|
8471
|
+
stableStateCache.set(createRandom, state);
|
|
8546
8472
|
return state;
|
|
8547
8473
|
}
|
|
8548
8474
|
/**
|
|
8549
|
-
* Octopus 3D
|
|
8475
|
+
* Octopus 3D 3 avatar visual.
|
|
8550
8476
|
*
|
|
8551
8477
|
* @private built-in avatar visual
|
|
8552
8478
|
*/
|
|
8553
|
-
const
|
|
8554
|
-
id: '
|
|
8555
|
-
title: 'Octopus 3D
|
|
8556
|
-
description: '
|
|
8479
|
+
const octopus3d3AvatarVisual = {
|
|
8480
|
+
id: 'octopus3d3',
|
|
8481
|
+
title: 'Octopus 3D 3',
|
|
8482
|
+
description: 'Cute continuous 3D octopus with a blobby single mesh, waving tentacle lobes, rich shading, and cursor-aware eyes.',
|
|
8557
8483
|
isAnimated: true,
|
|
8558
8484
|
supportsPointerTracking: true,
|
|
8559
8485
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
8560
|
-
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } =
|
|
8486
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } = getOctopus3d3StableState(createRandom);
|
|
8561
8487
|
const sceneCenterX = size * 0.5;
|
|
8562
|
-
const sceneCenterY = size * 0.
|
|
8563
|
-
const bob = Math.sin(timeMs /
|
|
8488
|
+
const sceneCenterY = size * 0.535;
|
|
8489
|
+
const bob = Math.sin(timeMs / 960 + animationPhase) * size * 0.012;
|
|
8564
8490
|
const meshCenter = {
|
|
8565
|
-
x: interaction.bodyOffsetX * size * 0.
|
|
8566
|
-
y: -size * 0.
|
|
8491
|
+
x: interaction.bodyOffsetX * size * 0.048 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
8492
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.026 + bob,
|
|
8567
8493
|
z: interaction.intensity * size * 0.018,
|
|
8568
8494
|
};
|
|
8569
|
-
const rotationY = -0.
|
|
8570
|
-
Math.sin(timeMs /
|
|
8571
|
-
interaction.bodyOffsetX * 0.
|
|
8572
|
-
interaction.gazeX * 0.
|
|
8573
|
-
const rotationX = -0.
|
|
8574
|
-
Math.cos(timeMs /
|
|
8495
|
+
const rotationY = -0.1 +
|
|
8496
|
+
Math.sin(timeMs / 2700 + animationPhase) * 0.035 +
|
|
8497
|
+
interaction.bodyOffsetX * 0.22 +
|
|
8498
|
+
interaction.gazeX * 0.88;
|
|
8499
|
+
const rotationX = -0.07 +
|
|
8500
|
+
Math.cos(timeMs / 3100 + animationPhase * 0.7) * 0.018 -
|
|
8575
8501
|
interaction.bodyOffsetY * 0.08 -
|
|
8576
|
-
interaction.gazeY * 0.
|
|
8502
|
+
interaction.gazeY * 0.38;
|
|
8577
8503
|
const surfaceOptions = {
|
|
8578
|
-
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.
|
|
8579
|
-
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.
|
|
8504
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.1,
|
|
8505
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.08,
|
|
8580
8506
|
radiusZ: size *
|
|
8581
8507
|
morphologyProfile.body.bodyRadiusRatio *
|
|
8582
|
-
(
|
|
8508
|
+
(1.02 + (morphologyProfile.body.horizontalStretch - 1) * 0.18),
|
|
8583
8509
|
morphologyProfile,
|
|
8584
8510
|
timeMs,
|
|
8585
8511
|
animationPhase,
|
|
8512
|
+
tentacleProfiles,
|
|
8586
8513
|
};
|
|
8587
|
-
const surfacePatches =
|
|
8514
|
+
const surfacePatches = resolveVisibleContinuousOctopusPatches({
|
|
8588
8515
|
...surfaceOptions,
|
|
8589
8516
|
center: meshCenter,
|
|
8590
8517
|
rotationX,
|
|
@@ -8594,62 +8521,109 @@ const octopus3d2AvatarVisual = {
|
|
|
8594
8521
|
size,
|
|
8595
8522
|
palette,
|
|
8596
8523
|
});
|
|
8597
|
-
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.
|
|
8598
|
-
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.
|
|
8599
|
-
const mouthLatitude = clampNumber$1(eyeLatitude + 0.
|
|
8600
|
-
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.
|
|
8601
|
-
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.
|
|
8602
|
-
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.
|
|
8524
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.2 - 0.03, -0.22, 0.08);
|
|
8525
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.1, 0.18, 0.32);
|
|
8526
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.2 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
8527
|
+
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
8528
|
+
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
8529
|
+
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
8530
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.76;
|
|
8531
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.9;
|
|
8603
8532
|
drawAvatarFrame(context, size, palette);
|
|
8604
|
-
|
|
8605
|
-
|
|
8533
|
+
drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
8534
|
+
drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
8606
8535
|
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
8607
|
-
|
|
8536
|
+
drawContinuousSurfacePatch(context, surfacePatch);
|
|
8608
8537
|
}
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8538
|
+
drawProjectedSurfaceCurrents({
|
|
8539
|
+
context,
|
|
8540
|
+
surfaceOptions,
|
|
8541
|
+
center: meshCenter,
|
|
8542
|
+
rotationX,
|
|
8543
|
+
rotationY,
|
|
8544
|
+
sceneCenterX,
|
|
8545
|
+
sceneCenterY,
|
|
8546
|
+
size,
|
|
8547
|
+
palette,
|
|
8548
|
+
morphologyProfile,
|
|
8549
|
+
timeMs,
|
|
8550
|
+
animationPhase,
|
|
8551
|
+
});
|
|
8552
|
+
drawProjectedTentacleSuckers({
|
|
8553
|
+
context,
|
|
8554
|
+
surfaceOptions,
|
|
8555
|
+
center: meshCenter,
|
|
8556
|
+
rotationX,
|
|
8557
|
+
rotationY,
|
|
8558
|
+
sceneCenterX,
|
|
8559
|
+
sceneCenterY,
|
|
8560
|
+
size,
|
|
8561
|
+
palette,
|
|
8562
|
+
});
|
|
8563
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8564
|
+
drawProjectedOrganicEye(context, sampleContinuousOctopusSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
8615
8565
|
drawProjectedOrganicMouth(context, [
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8566
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
8567
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
8568
|
+
sampleContinuousOctopusSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
8619
8569
|
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
8620
8570
|
},
|
|
8621
8571
|
};
|
|
8622
8572
|
/**
|
|
8623
|
-
*
|
|
8573
|
+
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
8624
8574
|
*
|
|
8625
|
-
* @private helper of `
|
|
8575
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8626
8576
|
*/
|
|
8627
|
-
function
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8577
|
+
function createContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
8578
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT$1 }, (_, tentacleIndex) => {
|
|
8579
|
+
const tentacleRandom = createRandom(`octopus3d3-tentacle-${tentacleIndex}`);
|
|
8580
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT$1 - 1);
|
|
8581
|
+
return {
|
|
8582
|
+
centerLongitude: -Math.PI * 0.86 +
|
|
8583
|
+
progress * Math.PI * 1.72 +
|
|
8584
|
+
(tentacleRandom() - 0.5) * (0.08 + morphologyProfile.tentacles.rootSpreadScale * 0.03),
|
|
8585
|
+
widthScale: 0.86 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.16,
|
|
8586
|
+
lengthScale: 0.86 + tentacleRandom() * 0.36 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.22,
|
|
8587
|
+
swayScale: 0.82 + tentacleRandom() * 0.38 + (morphologyProfile.tentacles.swayScale - 1) * 0.2,
|
|
8588
|
+
depthScale: 0.86 + tentacleRandom() * 0.32 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.2,
|
|
8589
|
+
phase: tentacleRandom() * Math.PI * 2,
|
|
8590
|
+
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
8591
|
+
};
|
|
8592
|
+
});
|
|
8593
|
+
}
|
|
8594
|
+
/**
|
|
8595
|
+
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
8596
|
+
*
|
|
8597
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8598
|
+
*/
|
|
8599
|
+
function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
8600
|
+
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));
|
|
8601
|
+
glowGradient.addColorStop(0, `${palette.highlight}66`);
|
|
8602
|
+
glowGradient.addColorStop(0.34, `${palette.accent}2e`);
|
|
8631
8603
|
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
8632
8604
|
context.fillStyle = glowGradient;
|
|
8633
8605
|
context.fillRect(0, 0, size, size);
|
|
8634
|
-
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs /
|
|
8635
|
-
lowerGradient.addColorStop(0, `${palette.secondary}
|
|
8606
|
+
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);
|
|
8607
|
+
lowerGradient.addColorStop(0, `${palette.secondary}25`);
|
|
8636
8608
|
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
8637
8609
|
context.fillStyle = lowerGradient;
|
|
8638
8610
|
context.fillRect(0, 0, size, size);
|
|
8639
8611
|
}
|
|
8640
8612
|
/**
|
|
8641
|
-
* Draws the soft
|
|
8613
|
+
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
8642
8614
|
*
|
|
8643
8615
|
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
8644
8616
|
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
8645
8617
|
*
|
|
8646
|
-
* @private helper of `
|
|
8618
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8647
8619
|
*/
|
|
8648
|
-
function
|
|
8620
|
+
function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
8649
8621
|
const cx = size * 0.5 + interaction.gazeX * size * 0.045;
|
|
8650
|
-
const cy = size * 0.
|
|
8651
|
-
const rx = size * (0.
|
|
8652
|
-
const ry = size * 0.
|
|
8622
|
+
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
8623
|
+
const rx = size * (0.19 + morphologyProfile.tentacles.rootSpreadScale * 0.022 + interaction.intensity * 0.02);
|
|
8624
|
+
const ry = size * 0.06;
|
|
8625
|
+
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
8626
|
+
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
8653
8627
|
context.save();
|
|
8654
8628
|
context.translate(cx, cy);
|
|
8655
8629
|
context.scale(1, ry / rx);
|
|
@@ -8666,32 +8640,32 @@ function drawBlobbyOctopusShadow(context, size, palette, interaction, timeMs, mo
|
|
|
8666
8640
|
context.restore();
|
|
8667
8641
|
}
|
|
8668
8642
|
/**
|
|
8669
|
-
* Number of latitude segments used by the
|
|
8643
|
+
* Number of latitude segments used by the continuous Octopus 3D 3 mesh.
|
|
8670
8644
|
*
|
|
8671
|
-
* @private helper of `
|
|
8645
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8672
8646
|
*/
|
|
8673
|
-
const LATITUDE_PATCH_COUNT$
|
|
8647
|
+
const LATITUDE_PATCH_COUNT$1 = 16;
|
|
8674
8648
|
/**
|
|
8675
|
-
* Number of longitude segments used by the
|
|
8649
|
+
* Number of longitude segments used by the continuous Octopus 3D 3 mesh.
|
|
8676
8650
|
*
|
|
8677
|
-
* @private helper of `
|
|
8651
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8678
8652
|
*/
|
|
8679
|
-
const LONGITUDE_PATCH_COUNT$
|
|
8653
|
+
const LONGITUDE_PATCH_COUNT$1 = 40;
|
|
8680
8654
|
/**
|
|
8681
|
-
* Resolves
|
|
8655
|
+
* Resolves visible projected patches for the continuous octopus mesh.
|
|
8682
8656
|
*
|
|
8683
|
-
* Within a single frame, mesh corner samples and longitude-only
|
|
8684
|
-
* quantized to the patch grid and computed once each rather
|
|
8685
|
-
* patch corner — the patch loop alone
|
|
8686
|
-
* `latitudePatchCount * longitudePatchCount * 4`
|
|
8687
|
-
*
|
|
8657
|
+
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
8658
|
+
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
8659
|
+
* than re-evaluated for every patch corner — the patch loop alone calls `sampleContinuousOctopusSurfacePoint`
|
|
8660
|
+
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
8661
|
+
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
8688
8662
|
*
|
|
8689
|
-
* @private helper of `
|
|
8663
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8690
8664
|
*/
|
|
8691
|
-
function
|
|
8692
|
-
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette
|
|
8693
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT$
|
|
8694
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT$
|
|
8665
|
+
function resolveVisibleContinuousOctopusPatches(options) {
|
|
8666
|
+
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
8667
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT$1;
|
|
8668
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT$1;
|
|
8695
8669
|
const surfacePatches = [];
|
|
8696
8670
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
8697
8671
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -8701,9 +8675,21 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
8701
8675
|
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
8702
8676
|
longitudeBoundaries[boundaryIndex] = -Math.PI + (boundaryIndex / longitudePatchCount) * Math.PI * 2;
|
|
8703
8677
|
}
|
|
8678
|
+
const cachedTentacleInfluencesByCornerLongitude = new Array(longitudePatchCount + 1);
|
|
8704
8679
|
const cachedLobeWavesByCornerLongitude = new Float64Array(longitudePatchCount + 1);
|
|
8680
|
+
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
8681
|
+
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
8682
|
+
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
8705
8683
|
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
8706
|
-
|
|
8684
|
+
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
8685
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveContinuousTentacleInfluence(options, cornerLongitude);
|
|
8686
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveContinuousLobeWave(options, cornerLongitude);
|
|
8687
|
+
}
|
|
8688
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
8689
|
+
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
8690
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveContinuousTentacleInfluence(options, patchCenterLongitude);
|
|
8691
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveContinuousLobeWave(options, patchCenterLongitude);
|
|
8692
|
+
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
8707
8693
|
}
|
|
8708
8694
|
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
8709
8695
|
const transformedCornerSamples = new Array(cornerCount);
|
|
@@ -8712,7 +8698,7 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
8712
8698
|
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
8713
8699
|
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
8714
8700
|
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
8715
|
-
const cornerSample =
|
|
8701
|
+
const cornerSample = sampleContinuousOctopusSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
8716
8702
|
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
8717
8703
|
}
|
|
8718
8704
|
}
|
|
@@ -8724,9 +8710,6 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
8724
8710
|
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
8725
8711
|
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
8726
8712
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
8727
|
-
const startLongitude = longitudeBoundaries[longitudeIndex];
|
|
8728
|
-
const endLongitude = longitudeBoundaries[longitudeIndex + 1];
|
|
8729
|
-
const centerLongitude = (startLongitude + endLongitude) / 2;
|
|
8730
8713
|
const transformedCorners = [
|
|
8731
8714
|
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
8732
8715
|
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
@@ -8734,7 +8717,7 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
8734
8717
|
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
8735
8718
|
];
|
|
8736
8719
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
8737
|
-
if (surfaceNormal.z <= 0.
|
|
8720
|
+
if (surfaceNormal.z <= 0.008) {
|
|
8738
8721
|
continue;
|
|
8739
8722
|
}
|
|
8740
8723
|
const projectedCorners = [
|
|
@@ -8751,99 +8734,183 @@ function resolveVisibleBlobbyOctopusPatches(options) {
|
|
|
8751
8734
|
transformedCorners[3].z) /
|
|
8752
8735
|
4,
|
|
8753
8736
|
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$2), -1, 1),
|
|
8754
|
-
fillStyle:
|
|
8755
|
-
outlineColor: verticalProgress < 0.
|
|
8737
|
+
fillStyle: resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
8738
|
+
outlineColor: verticalProgress < 0.54 ? `${palette.highlight}69` : `${palette.shadow}78`,
|
|
8756
8739
|
});
|
|
8757
8740
|
}
|
|
8758
8741
|
}
|
|
8759
8742
|
return surfacePatches;
|
|
8760
8743
|
}
|
|
8761
8744
|
/**
|
|
8762
|
-
* Samples one point on the continuous Octopus 3D
|
|
8745
|
+
* Samples one point on the continuous Octopus 3D 3 surface.
|
|
8763
8746
|
*
|
|
8764
|
-
* The lower hemisphere
|
|
8765
|
-
*
|
|
8747
|
+
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
8748
|
+
* tentacled while still being rendered as one connected blobby mesh.
|
|
8766
8749
|
*
|
|
8767
|
-
* @private helper of `
|
|
8750
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8768
8751
|
*/
|
|
8769
|
-
function
|
|
8770
|
-
|
|
8771
|
-
return sampleBlobbyOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveLowerLobeWave(longitude, morphologyProfile, animationPhase, timeMs));
|
|
8752
|
+
function sampleContinuousOctopusSurfacePoint(options, latitude, longitude) {
|
|
8753
|
+
return sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, resolveContinuousTentacleInfluence(options, longitude), resolveContinuousLobeWave(options, longitude));
|
|
8772
8754
|
}
|
|
8773
8755
|
/**
|
|
8774
|
-
* Samples one point on the continuous Octopus 3D
|
|
8775
|
-
* to skip the per-call
|
|
8756
|
+
* Samples one point on the continuous Octopus 3D 3 surface using precomputed longitude-only
|
|
8757
|
+
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
8776
8758
|
*
|
|
8777
|
-
*
|
|
8759
|
+
* The patch loop quantizes the mesh into a fixed `(latitudePatchCount + 1) * (longitudePatchCount + 1)`
|
|
8760
|
+
* corner grid, so the same longitude is reused across every latitude row and each
|
|
8761
|
+
* tentacle/lobe value can be computed once per frame instead of `latitudePatchCount * 4` times.
|
|
8762
|
+
*
|
|
8763
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8778
8764
|
*/
|
|
8779
|
-
function
|
|
8765
|
+
function sampleContinuousOctopusSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
8780
8766
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
8781
8767
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
8782
8768
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
8783
|
-
const upperBlend = Math.pow(1 - verticalProgress, 1.
|
|
8784
|
-
const lowerBlend =
|
|
8785
|
-
const
|
|
8786
|
-
const
|
|
8787
|
-
|
|
8769
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.28);
|
|
8770
|
+
const lowerBlend = smoothStep$1(0.38, 1, verticalProgress);
|
|
8771
|
+
const tipBlend = smoothStep$1(0.68, 1, verticalProgress);
|
|
8772
|
+
const centerPull = resolveSignedAngularDistance$1(longitude, tentacleInfluence.centerLongitude);
|
|
8773
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.24 + tipBlend * 0.2);
|
|
8774
|
+
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
8775
|
+
animationPhase * 0.6 +
|
|
8776
|
+
timeMs / (1750 + morphologyProfile.body.lobeCount * 30)) *
|
|
8777
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.8) *
|
|
8778
|
+
(0.3 + lowerBlend * 0.7);
|
|
8779
|
+
const tentacleWave = Math.sin(timeMs / 760 + tentacleInfluence.phase + verticalProgress * 2.4) *
|
|
8780
|
+
lowerBlend *
|
|
8781
|
+
tentacleInfluence.core *
|
|
8782
|
+
tentacleInfluence.swayScale;
|
|
8783
|
+
const horizontalScale = 1.04 +
|
|
8784
|
+
mantleRipple +
|
|
8785
|
+
lowerBlend * (0.16 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.1) +
|
|
8786
|
+
lowerBlend * tentacleInfluence.core * (0.2 + lowerLobeWave * 0.12) -
|
|
8788
8787
|
upperBlend * 0.08;
|
|
8789
|
-
const depthScale = 1.
|
|
8788
|
+
const depthScale = 1.06 +
|
|
8790
8789
|
upperBlend * 0.16 +
|
|
8791
|
-
Math.max(0, Math.cos(
|
|
8792
|
-
|
|
8793
|
-
Math.max(0, -Math.cos(
|
|
8794
|
-
const
|
|
8790
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.1 +
|
|
8791
|
+
lowerBlend * tentacleInfluence.core * (0.1 + tentacleInfluence.depthScale * 0.06) -
|
|
8792
|
+
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
8793
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.11 + tipBlend * 0.06 + tentacleInfluence.widthScale * 0.025) * radiusX;
|
|
8794
|
+
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
8795
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.72;
|
|
8796
|
+
const lowerDrop = lowerBlend *
|
|
8795
8797
|
radiusY *
|
|
8796
|
-
(0.
|
|
8797
|
-
|
|
8798
|
-
|
|
8798
|
+
(0.18 +
|
|
8799
|
+
tentacleInfluence.core *
|
|
8800
|
+
(0.38 +
|
|
8801
|
+
tentacleInfluence.lengthScale * 0.22 +
|
|
8802
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.08));
|
|
8799
8803
|
return {
|
|
8800
|
-
x: Math.sin(
|
|
8801
|
-
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.
|
|
8804
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + tentacleWave * radiusX * (0.052 + tipBlend * 0.05),
|
|
8805
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.12) -
|
|
8802
8806
|
upperBlend * radiusY * 0.1 +
|
|
8803
8807
|
lowerDrop +
|
|
8804
|
-
Math.sin(timeMs /
|
|
8805
|
-
|
|
8808
|
+
Math.sin(timeMs / 1420 + animationPhase + latitude * 1.6) * lowerBlend * radiusY * 0.018 +
|
|
8809
|
+
Math.cos(timeMs / 880 + tentacleInfluence.phase) *
|
|
8810
|
+
lowerBlend *
|
|
8811
|
+
tipBlend *
|
|
8812
|
+
tentacleInfluence.core *
|
|
8813
|
+
radiusY *
|
|
8814
|
+
0.034,
|
|
8815
|
+
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
8816
|
+
Math.cos(timeMs / 980 + tentacleInfluence.phase + verticalProgress) *
|
|
8817
|
+
lowerBlend *
|
|
8818
|
+
tentacleInfluence.core *
|
|
8819
|
+
radiusZ *
|
|
8820
|
+
0.04,
|
|
8806
8821
|
};
|
|
8807
8822
|
}
|
|
8808
8823
|
/**
|
|
8809
|
-
*
|
|
8824
|
+
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
8810
8825
|
*
|
|
8811
|
-
* @private helper of `
|
|
8826
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8812
8827
|
*/
|
|
8813
|
-
function
|
|
8814
|
-
|
|
8815
|
-
|
|
8828
|
+
function resolveContinuousTentacleInfluence(options, longitude) {
|
|
8829
|
+
let totalWeight = 0;
|
|
8830
|
+
let weightedSin = 0;
|
|
8831
|
+
let weightedCos = 0;
|
|
8832
|
+
let weightedWidthScale = 0;
|
|
8833
|
+
let weightedLengthScale = 0;
|
|
8834
|
+
let weightedSwayScale = 0;
|
|
8835
|
+
let weightedDepthScale = 0;
|
|
8836
|
+
let weightedPhase = 0;
|
|
8837
|
+
for (const tentacleProfile of options.tentacleProfiles) {
|
|
8838
|
+
const distance = Math.abs(resolveSignedAngularDistance$1(longitude, tentacleProfile.centerLongitude));
|
|
8839
|
+
const width = 0.2 * tentacleProfile.widthScale;
|
|
8840
|
+
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
8841
|
+
totalWeight += weight;
|
|
8842
|
+
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
8843
|
+
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
8844
|
+
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
8845
|
+
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
8846
|
+
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
8847
|
+
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
8848
|
+
weightedPhase += tentacleProfile.phase * weight;
|
|
8849
|
+
}
|
|
8850
|
+
if (totalWeight < 0.0001) {
|
|
8851
|
+
return {
|
|
8852
|
+
core: 0,
|
|
8853
|
+
centerLongitude: longitude,
|
|
8854
|
+
widthScale: 1,
|
|
8855
|
+
lengthScale: 1,
|
|
8856
|
+
swayScale: 1,
|
|
8857
|
+
depthScale: 1,
|
|
8858
|
+
phase: 0,
|
|
8859
|
+
};
|
|
8860
|
+
}
|
|
8861
|
+
return {
|
|
8862
|
+
core: clampNumber$1(totalWeight, 0, 1),
|
|
8863
|
+
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
8864
|
+
widthScale: weightedWidthScale / totalWeight,
|
|
8865
|
+
lengthScale: weightedLengthScale / totalWeight,
|
|
8866
|
+
swayScale: weightedSwayScale / totalWeight,
|
|
8867
|
+
depthScale: weightedDepthScale / totalWeight,
|
|
8868
|
+
phase: weightedPhase / totalWeight,
|
|
8869
|
+
};
|
|
8816
8870
|
}
|
|
8817
8871
|
/**
|
|
8818
|
-
* Resolves
|
|
8872
|
+
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
8819
8873
|
*
|
|
8820
|
-
* @private helper of `
|
|
8874
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8821
8875
|
*/
|
|
8822
|
-
function
|
|
8823
|
-
const
|
|
8824
|
-
|
|
8876
|
+
function resolveContinuousLobeWave(options, longitude) {
|
|
8877
|
+
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
8878
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT$1 +
|
|
8879
|
+
animationPhase +
|
|
8880
|
+
timeMs / (980 + morphologyProfile.body.lobeCount * 18)) +
|
|
8881
|
+
1) /
|
|
8882
|
+
2);
|
|
8883
|
+
}
|
|
8884
|
+
/**
|
|
8885
|
+
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
8886
|
+
*
|
|
8887
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8888
|
+
*/
|
|
8889
|
+
function resolveContinuousSurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
8890
|
+
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
8891
|
+
if (tonalProgress < 0.14) {
|
|
8825
8892
|
return palette.highlight;
|
|
8826
8893
|
}
|
|
8827
|
-
if (tonalProgress < 0.
|
|
8894
|
+
if (tonalProgress < 0.32) {
|
|
8828
8895
|
return palette.secondary;
|
|
8829
8896
|
}
|
|
8830
8897
|
if (tonalProgress < 0.72) {
|
|
8831
|
-
return forwardness > 0.
|
|
8898
|
+
return forwardness > 0.55 ? palette.secondary : palette.primary;
|
|
8832
8899
|
}
|
|
8833
|
-
return `${palette.shadow}
|
|
8900
|
+
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
8834
8901
|
}
|
|
8835
8902
|
/**
|
|
8836
|
-
* Draws one projected patch with soft
|
|
8903
|
+
* Draws one projected mesh patch with soft shading and a subtle edge.
|
|
8837
8904
|
*
|
|
8838
|
-
* @private helper of `
|
|
8905
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
8839
8906
|
*/
|
|
8840
|
-
function
|
|
8907
|
+
function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
8841
8908
|
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
8842
8909
|
if (surfacePatch.lightIntensity > 0) {
|
|
8843
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.
|
|
8910
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.18 * surfacePatch.lightIntensity})`);
|
|
8844
8911
|
}
|
|
8845
8912
|
else if (surfacePatch.lightIntensity < 0) {
|
|
8846
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.
|
|
8913
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.25 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
8847
8914
|
}
|
|
8848
8915
|
context.save();
|
|
8849
8916
|
context.beginPath();
|
|
@@ -8853,51 +8920,180 @@ function drawBlobbySurfacePatch(context, surfacePatch) {
|
|
|
8853
8920
|
}
|
|
8854
8921
|
context.closePath();
|
|
8855
8922
|
context.strokeStyle = surfacePatch.outlineColor;
|
|
8856
|
-
context.lineWidth = Math.max(
|
|
8923
|
+
context.lineWidth = Math.max(0.7, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0032);
|
|
8857
8924
|
context.lineJoin = 'round';
|
|
8858
8925
|
context.stroke();
|
|
8859
8926
|
context.restore();
|
|
8860
8927
|
}
|
|
8861
|
-
|
|
8862
|
-
/* eslint-disable no-magic-numbers */
|
|
8863
8928
|
/**
|
|
8864
|
-
*
|
|
8929
|
+
* Draws projected mantle-current lines on the front of the mesh.
|
|
8865
8930
|
*
|
|
8866
8931
|
* @private helper of `octopus3d3AvatarVisual`
|
|
8867
8932
|
*/
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
|
|
8872
|
-
|
|
8933
|
+
function drawProjectedSurfaceCurrents(options) {
|
|
8934
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
8935
|
+
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
8936
|
+
const centerIndex = (currentCount - 1) / 2;
|
|
8937
|
+
context.save();
|
|
8938
|
+
context.lineCap = 'round';
|
|
8939
|
+
context.lineJoin = 'round';
|
|
8940
|
+
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
8941
|
+
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
8942
|
+
const projectedPoints = [];
|
|
8943
|
+
for (let sampleIndex = 0; sampleIndex < 8; sampleIndex++) {
|
|
8944
|
+
const progress = sampleIndex / 7;
|
|
8945
|
+
const latitude = -0.46 + progress * 0.74;
|
|
8946
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1160 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.035;
|
|
8947
|
+
const scenePoint = transformScenePoint(sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
8948
|
+
if (scenePoint.z > center.z - size * 0.016) {
|
|
8949
|
+
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
8950
|
+
}
|
|
8951
|
+
}
|
|
8952
|
+
if (projectedPoints.length < 3) {
|
|
8953
|
+
continue;
|
|
8954
|
+
}
|
|
8955
|
+
context.beginPath();
|
|
8956
|
+
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
8957
|
+
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
8958
|
+
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
8959
|
+
}
|
|
8960
|
+
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
8961
|
+
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
8962
|
+
context.stroke();
|
|
8963
|
+
}
|
|
8964
|
+
context.restore();
|
|
8965
|
+
}
|
|
8873
8966
|
/**
|
|
8874
|
-
*
|
|
8967
|
+
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
8875
8968
|
*
|
|
8876
8969
|
* @private helper of `octopus3d3AvatarVisual`
|
|
8877
8970
|
*/
|
|
8878
|
-
|
|
8971
|
+
function drawProjectedTentacleSuckers(options) {
|
|
8972
|
+
const { surfaceOptions, size } = options;
|
|
8973
|
+
const { timeMs } = surfaceOptions;
|
|
8974
|
+
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
8975
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.12) {
|
|
8976
|
+
continue;
|
|
8977
|
+
}
|
|
8978
|
+
for (let suckerIndex = 0; suckerIndex < 3; suckerIndex++) {
|
|
8979
|
+
const latitude = 0.52 + suckerIndex * 0.14;
|
|
8980
|
+
const sideOffset = tentacleProfile.suckerSide * (0.035 + suckerIndex * 0.012) * tentacleProfile.widthScale;
|
|
8981
|
+
const waveOffset = Math.sin(timeMs / 900 + tentacleProfile.phase + suckerIndex * 0.8) * 0.018;
|
|
8982
|
+
drawProjectedSurfaceSpot({
|
|
8983
|
+
...options,
|
|
8984
|
+
latitude,
|
|
8985
|
+
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
8986
|
+
radiusScale: size * (0.0065 - suckerIndex * 0.0007),
|
|
8987
|
+
});
|
|
8988
|
+
}
|
|
8989
|
+
}
|
|
8990
|
+
}
|
|
8879
8991
|
/**
|
|
8880
|
-
*
|
|
8881
|
-
* mounted `<Avatar/>` component (created inside `resolveAvatarRenderDefinition` and held in a
|
|
8882
|
-
* React `useMemo`). Using a `WeakMap` ensures the entry is collected when the component unmounts.
|
|
8992
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
8883
8993
|
*
|
|
8884
8994
|
* @private helper of `octopus3d3AvatarVisual`
|
|
8885
8995
|
*/
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
8996
|
+
function drawProjectedSurfaceSpot(options) {
|
|
8997
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
8998
|
+
const localCenter = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude);
|
|
8999
|
+
const localHorizontal = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
9000
|
+
const localVertical = sampleContinuousOctopusSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
9001
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
9002
|
+
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
9003
|
+
return;
|
|
9004
|
+
}
|
|
9005
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
9006
|
+
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
9007
|
+
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
9008
|
+
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
9009
|
+
radiusScale *
|
|
9010
|
+
0.74, size * 0.003, size * 0.018);
|
|
9011
|
+
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
9012
|
+
radiusScale *
|
|
9013
|
+
0.52, size * 0.0024, size * 0.014);
|
|
9014
|
+
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
9015
|
+
context.save();
|
|
9016
|
+
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
9017
|
+
context.rotate(rotation);
|
|
9018
|
+
context.beginPath();
|
|
9019
|
+
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
9020
|
+
context.fillStyle = `${palette.highlight}73`;
|
|
9021
|
+
context.fill();
|
|
9022
|
+
context.strokeStyle = `${palette.highlight}99`;
|
|
9023
|
+
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
9024
|
+
context.stroke();
|
|
9025
|
+
context.restore();
|
|
9026
|
+
}
|
|
9027
|
+
/**
|
|
9028
|
+
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
8890
9029
|
*
|
|
8891
9030
|
* @private helper of `octopus3d3AvatarVisual`
|
|
8892
9031
|
*/
|
|
8893
|
-
function
|
|
8894
|
-
|
|
9032
|
+
function resolveSignedAngularDistance$1(sourceLongitude, targetLongitude) {
|
|
9033
|
+
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
9034
|
+
}
|
|
9035
|
+
/**
|
|
9036
|
+
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
9037
|
+
*
|
|
9038
|
+
* @private helper of `octopus3d3AvatarVisual`
|
|
9039
|
+
*/
|
|
9040
|
+
function smoothStep$1(edgeStart, edgeEnd, value) {
|
|
9041
|
+
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
9042
|
+
return progress * progress * (3 - 2 * progress);
|
|
9043
|
+
}
|
|
9044
|
+
|
|
9045
|
+
/* eslint-disable no-magic-numbers */
|
|
9046
|
+
/**
|
|
9047
|
+
* Light direction used by the continuous Octopus 3D 4 mesh shading.
|
|
9048
|
+
*
|
|
9049
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9050
|
+
*/
|
|
9051
|
+
const LIGHT_DIRECTION$1 = normalizeVector3({
|
|
9052
|
+
x: 0.32,
|
|
9053
|
+
y: -0.66,
|
|
9054
|
+
z: 1,
|
|
9055
|
+
});
|
|
9056
|
+
/**
|
|
9057
|
+
* Rim-light direction used to brighten the side of the silhouette.
|
|
9058
|
+
*
|
|
9059
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9060
|
+
*/
|
|
9061
|
+
const RIM_LIGHT_DIRECTION = normalizeVector3({
|
|
9062
|
+
x: -0.7,
|
|
9063
|
+
y: -0.18,
|
|
9064
|
+
z: 0.55,
|
|
9065
|
+
});
|
|
9066
|
+
/**
|
|
9067
|
+
* Real-octopus tentacle count used by the continuous lower mesh.
|
|
9068
|
+
*
|
|
9069
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9070
|
+
*/
|
|
9071
|
+
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
9072
|
+
/**
|
|
9073
|
+
* Number of seeded skin spots painted across the octopus body.
|
|
9074
|
+
*
|
|
9075
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9076
|
+
*/
|
|
9077
|
+
const SKIN_SPOT_COUNT = 14;
|
|
9078
|
+
/**
|
|
9079
|
+
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
9080
|
+
*
|
|
9081
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9082
|
+
*/
|
|
9083
|
+
const octopus3d4StableStateCache = new WeakMap();
|
|
9084
|
+
/**
|
|
9085
|
+
* Returns the stable per-avatar state, computing it on first access and caching afterwards.
|
|
9086
|
+
*
|
|
9087
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9088
|
+
*/
|
|
9089
|
+
function getOctopus3d4StableState(createRandom) {
|
|
9090
|
+
const cached = octopus3d4StableStateCache.get(createRandom);
|
|
8895
9091
|
if (cached !== undefined) {
|
|
8896
9092
|
return cached;
|
|
8897
9093
|
}
|
|
8898
9094
|
const morphologyProfile = createOctopus3MorphologyProfile(createRandom);
|
|
8899
|
-
const animationRandom = createRandom('
|
|
8900
|
-
const eyeRandom = createRandom('
|
|
9095
|
+
const animationRandom = createRandom('octopus3d4-animation-profile');
|
|
9096
|
+
const eyeRandom = createRandom('octopus3d4-eye-profile');
|
|
8901
9097
|
const leftEyePhaseOffset = eyeRandom() * 0.7;
|
|
8902
9098
|
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
8903
9099
|
const state = {
|
|
@@ -8905,52 +9101,53 @@ function getOctopus3d3StableState(createRandom) {
|
|
|
8905
9101
|
animationPhase: animationRandom() * Math.PI * 2,
|
|
8906
9102
|
leftEyePhaseOffset,
|
|
8907
9103
|
rightEyePhaseOffset,
|
|
8908
|
-
tentacleProfiles:
|
|
9104
|
+
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
9105
|
+
skinSpots: createBlobbySkinSpots(createRandom),
|
|
8909
9106
|
};
|
|
8910
|
-
|
|
9107
|
+
octopus3d4StableStateCache.set(createRandom, state);
|
|
8911
9108
|
return state;
|
|
8912
9109
|
}
|
|
8913
9110
|
/**
|
|
8914
|
-
* Octopus 3D
|
|
9111
|
+
* Octopus 3D 4 avatar visual.
|
|
8915
9112
|
*
|
|
8916
9113
|
* @private built-in avatar visual
|
|
8917
9114
|
*/
|
|
8918
|
-
const
|
|
8919
|
-
id: '
|
|
8920
|
-
title: 'Octopus 3D
|
|
8921
|
-
description: 'Cute
|
|
9115
|
+
const octopus3d4AvatarVisual = {
|
|
9116
|
+
id: 'octopus3d4',
|
|
9117
|
+
title: 'Octopus 3D 4',
|
|
9118
|
+
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.',
|
|
8922
9119
|
isAnimated: true,
|
|
8923
9120
|
supportsPointerTracking: true,
|
|
8924
9121
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
8925
|
-
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles } =
|
|
9122
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset, tentacleProfiles, skinSpots, } = getOctopus3d4StableState(createRandom);
|
|
8926
9123
|
const sceneCenterX = size * 0.5;
|
|
8927
9124
|
const sceneCenterY = size * 0.535;
|
|
8928
|
-
const bob = Math.sin(timeMs /
|
|
9125
|
+
const bob = Math.sin(timeMs / 980 + animationPhase) * size * 0.013;
|
|
8929
9126
|
const meshCenter = {
|
|
8930
|
-
x: interaction.bodyOffsetX * size * 0.
|
|
8931
|
-
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.
|
|
8932
|
-
z: interaction.intensity * size * 0.
|
|
9127
|
+
x: interaction.bodyOffsetX * size * 0.052 + size * morphologyProfile.body.centerXJitterRatio * 0.44,
|
|
9128
|
+
y: -size * 0.07 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
9129
|
+
z: interaction.intensity * size * 0.02,
|
|
8933
9130
|
};
|
|
8934
|
-
const rotationY = -0.
|
|
8935
|
-
Math.sin(timeMs /
|
|
8936
|
-
interaction.bodyOffsetX * 0.
|
|
8937
|
-
interaction.gazeX * 0.
|
|
9131
|
+
const rotationY = -0.08 +
|
|
9132
|
+
Math.sin(timeMs / 2800 + animationPhase) * 0.04 +
|
|
9133
|
+
interaction.bodyOffsetX * 0.24 +
|
|
9134
|
+
interaction.gazeX * 0.98;
|
|
8938
9135
|
const rotationX = -0.07 +
|
|
8939
|
-
Math.cos(timeMs /
|
|
9136
|
+
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
8940
9137
|
interaction.bodyOffsetY * 0.08 -
|
|
8941
|
-
interaction.gazeY * 0.
|
|
9138
|
+
interaction.gazeY * 0.42;
|
|
8942
9139
|
const surfaceOptions = {
|
|
8943
|
-
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.
|
|
8944
|
-
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.
|
|
9140
|
+
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
9141
|
+
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
8945
9142
|
radiusZ: size *
|
|
8946
9143
|
morphologyProfile.body.bodyRadiusRatio *
|
|
8947
|
-
(1.
|
|
9144
|
+
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
8948
9145
|
morphologyProfile,
|
|
8949
9146
|
timeMs,
|
|
8950
9147
|
animationPhase,
|
|
8951
9148
|
tentacleProfiles,
|
|
8952
9149
|
};
|
|
8953
|
-
const surfacePatches =
|
|
9150
|
+
const surfacePatches = resolveVisibleBlobbyContinuousPatches({
|
|
8954
9151
|
...surfaceOptions,
|
|
8955
9152
|
center: meshCenter,
|
|
8956
9153
|
rotationX,
|
|
@@ -8960,21 +9157,33 @@ const octopus3d3AvatarVisual = {
|
|
|
8960
9157
|
size,
|
|
8961
9158
|
palette,
|
|
8962
9159
|
});
|
|
8963
|
-
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.
|
|
8964
|
-
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.
|
|
8965
|
-
const mouthLatitude = clampNumber$1(eyeLatitude + 0.
|
|
9160
|
+
const eyeLatitude = clampNumber$1(morphologyProfile.face.eyeCenterYOffsetRatio * 4.4 - 0.04, -0.24, 0.08);
|
|
9161
|
+
const eyeLongitude = clampNumber$1(morphologyProfile.face.eyeSpacingRatio * 3.0, 0.18, 0.32);
|
|
9162
|
+
const mouthLatitude = clampNumber$1(eyeLatitude + 0.21 + morphologyProfile.face.mouthYOffsetRatio, 0.08, 0.34);
|
|
8966
9163
|
const mouthCenterLongitude = clampNumber$1(morphologyProfile.face.mouthCenterOffsetRatio * 5.6, -0.08, 0.08);
|
|
8967
9164
|
const mouthHalfLongitude = clampNumber$1(eyeLongitude * 0.78, 0.15, 0.28);
|
|
8968
9165
|
const mouthCurveLatitude = clampNumber$1(mouthLatitude + morphologyProfile.face.mouthCurveDepthRatio * 0.78, mouthLatitude + 0.03, 0.42);
|
|
8969
|
-
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.
|
|
8970
|
-
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.
|
|
9166
|
+
const eyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.78;
|
|
9167
|
+
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
8971
9168
|
drawAvatarFrame(context, size, palette);
|
|
8972
|
-
|
|
8973
|
-
|
|
9169
|
+
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
9170
|
+
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
8974
9171
|
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
8975
|
-
|
|
9172
|
+
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
8976
9173
|
}
|
|
8977
|
-
|
|
9174
|
+
drawBlobbySkinSpots({
|
|
9175
|
+
context,
|
|
9176
|
+
surfaceOptions,
|
|
9177
|
+
center: meshCenter,
|
|
9178
|
+
rotationX,
|
|
9179
|
+
rotationY,
|
|
9180
|
+
sceneCenterX,
|
|
9181
|
+
sceneCenterY,
|
|
9182
|
+
size,
|
|
9183
|
+
palette,
|
|
9184
|
+
skinSpots,
|
|
9185
|
+
});
|
|
9186
|
+
drawBlobbyContinuousCurrents({
|
|
8978
9187
|
context,
|
|
8979
9188
|
surfaceOptions,
|
|
8980
9189
|
center: meshCenter,
|
|
@@ -8988,7 +9197,7 @@ const octopus3d3AvatarVisual = {
|
|
|
8988
9197
|
timeMs,
|
|
8989
9198
|
animationPhase,
|
|
8990
9199
|
});
|
|
8991
|
-
|
|
9200
|
+
drawBlobbyContinuousSuckers({
|
|
8992
9201
|
context,
|
|
8993
9202
|
surfaceOptions,
|
|
8994
9203
|
center: meshCenter,
|
|
@@ -8999,51 +9208,78 @@ const octopus3d3AvatarVisual = {
|
|
|
8999
9208
|
size,
|
|
9000
9209
|
palette,
|
|
9001
9210
|
});
|
|
9002
|
-
|
|
9003
|
-
|
|
9211
|
+
drawBlobbyContinuousGloss({
|
|
9212
|
+
context,
|
|
9213
|
+
surfaceOptions,
|
|
9214
|
+
center: meshCenter,
|
|
9215
|
+
rotationX,
|
|
9216
|
+
rotationY,
|
|
9217
|
+
sceneCenterX,
|
|
9218
|
+
sceneCenterY,
|
|
9219
|
+
size,
|
|
9220
|
+
palette,
|
|
9221
|
+
});
|
|
9222
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, -eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9223
|
+
drawProjectedOrganicEye(context, sampleBlobbyContinuousSurfacePoint(surfaceOptions, eyeLatitude, eyeLongitude), eyeRadiusX, eyeRadiusY, meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.85 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9004
9224
|
drawProjectedOrganicMouth(context, [
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9225
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude - mouthHalfLongitude),
|
|
9226
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthCurveLatitude, mouthCenterLongitude),
|
|
9227
|
+
sampleBlobbyContinuousSurfacePoint(surfaceOptions, mouthLatitude, mouthCenterLongitude + mouthHalfLongitude),
|
|
9008
9228
|
], meshCenter, rotationX, rotationY, sceneCenterX, sceneCenterY, palette, size);
|
|
9009
9229
|
},
|
|
9010
9230
|
};
|
|
9011
9231
|
/**
|
|
9012
9232
|
* Creates seeded tentacle-lobe profiles around the visible lower octopus body.
|
|
9013
9233
|
*
|
|
9014
|
-
* @private helper of `
|
|
9234
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9015
9235
|
*/
|
|
9016
|
-
function
|
|
9017
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT
|
|
9018
|
-
const tentacleRandom = createRandom(`
|
|
9019
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT
|
|
9236
|
+
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
9237
|
+
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
9238
|
+
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
9239
|
+
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
9020
9240
|
return {
|
|
9021
|
-
centerLongitude: -Math.PI * 0.
|
|
9022
|
-
progress * Math.PI * 1.
|
|
9023
|
-
(tentacleRandom() - 0.5) * (0.
|
|
9024
|
-
widthScale: 0.
|
|
9025
|
-
lengthScale: 0.
|
|
9026
|
-
swayScale: 0.
|
|
9027
|
-
depthScale: 0.
|
|
9028
|
-
|
|
9241
|
+
centerLongitude: -Math.PI * 0.9 +
|
|
9242
|
+
progress * Math.PI * 1.8 +
|
|
9243
|
+
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
9244
|
+
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
9245
|
+
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
9246
|
+
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
9247
|
+
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
9248
|
+
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
9249
|
+
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9250
|
+
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9029
9251
|
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
9030
9252
|
};
|
|
9031
9253
|
});
|
|
9032
9254
|
}
|
|
9255
|
+
/**
|
|
9256
|
+
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
9257
|
+
*
|
|
9258
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9259
|
+
*/
|
|
9260
|
+
function createBlobbySkinSpots(createRandom) {
|
|
9261
|
+
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
9262
|
+
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
9263
|
+
latitude: -0.45 + spotRandom() * 0.7,
|
|
9264
|
+
longitude: -0.5 + spotRandom() * 1.0,
|
|
9265
|
+
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
9266
|
+
opacity: 0.16 + spotRandom() * 0.22,
|
|
9267
|
+
}));
|
|
9268
|
+
}
|
|
9033
9269
|
/**
|
|
9034
9270
|
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
9035
9271
|
*
|
|
9036
|
-
* @private helper of `
|
|
9272
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9037
9273
|
*/
|
|
9038
|
-
function
|
|
9039
|
-
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.
|
|
9040
|
-
glowGradient.addColorStop(0, `${palette.highlight}
|
|
9041
|
-
glowGradient.addColorStop(0.
|
|
9274
|
+
function drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
9275
|
+
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));
|
|
9276
|
+
glowGradient.addColorStop(0, `${palette.highlight}74`);
|
|
9277
|
+
glowGradient.addColorStop(0.32, `${palette.accent}32`);
|
|
9042
9278
|
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
9043
9279
|
context.fillStyle = glowGradient;
|
|
9044
9280
|
context.fillRect(0, 0, size, size);
|
|
9045
|
-
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs /
|
|
9046
|
-
lowerGradient.addColorStop(0, `${palette.secondary}
|
|
9281
|
+
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);
|
|
9282
|
+
lowerGradient.addColorStop(0, `${palette.secondary}28`);
|
|
9047
9283
|
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
9048
9284
|
context.fillStyle = lowerGradient;
|
|
9049
9285
|
context.fillRect(0, 0, size, size);
|
|
@@ -9051,26 +9287,21 @@ function drawContinuousOctopusAtmosphere(context, size, palette, sceneCenterX, s
|
|
|
9051
9287
|
/**
|
|
9052
9288
|
* Draws the soft lower shadow that anchors the octopus in the avatar frame.
|
|
9053
9289
|
*
|
|
9054
|
-
*
|
|
9055
|
-
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
9056
|
-
*
|
|
9057
|
-
* @private helper of `octopus3d3AvatarVisual`
|
|
9290
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9058
9291
|
*/
|
|
9059
|
-
function
|
|
9060
|
-
const cx = size * 0.5 + interaction.gazeX * size * 0.
|
|
9292
|
+
function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile) {
|
|
9293
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.046;
|
|
9061
9294
|
const cy = size * 0.9 + Math.sin(timeMs / 980) * size * 0.007;
|
|
9062
|
-
const rx = size * (0.
|
|
9063
|
-
const ry = size * 0.
|
|
9064
|
-
// Scale the context so that drawing a circle produces the correct ellipse aspect ratio,
|
|
9065
|
-
// then fill with a radial gradient that approximates the blurry edge without context.filter.
|
|
9295
|
+
const rx = size * (0.2 + morphologyProfile.tentacles.rootSpreadScale * 0.024 + interaction.intensity * 0.022);
|
|
9296
|
+
const ry = size * 0.062;
|
|
9066
9297
|
context.save();
|
|
9067
9298
|
context.translate(cx, cy);
|
|
9068
9299
|
context.scale(1, ry / rx);
|
|
9069
|
-
const blurRadius = rx * 1.
|
|
9300
|
+
const blurRadius = rx * 1.42;
|
|
9070
9301
|
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
9071
|
-
shadowGradient.addColorStop(0, `${palette.shadow}
|
|
9072
|
-
shadowGradient.addColorStop(0.45, `${palette.shadow}
|
|
9073
|
-
shadowGradient.addColorStop(0.8, `${palette.shadow}
|
|
9302
|
+
shadowGradient.addColorStop(0, `${palette.shadow}82`);
|
|
9303
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}4a`);
|
|
9304
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1c`);
|
|
9074
9305
|
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
9075
9306
|
context.fillStyle = shadowGradient;
|
|
9076
9307
|
context.beginPath();
|
|
@@ -9079,32 +9310,30 @@ function drawContinuousOctopusShadow(context, size, palette, interaction, timeMs
|
|
|
9079
9310
|
context.restore();
|
|
9080
9311
|
}
|
|
9081
9312
|
/**
|
|
9082
|
-
* Number of latitude segments used by the continuous Octopus 3D
|
|
9313
|
+
* Number of latitude segments used by the continuous Octopus 3D 4 mesh.
|
|
9083
9314
|
*
|
|
9084
|
-
* @private helper of `
|
|
9315
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9085
9316
|
*/
|
|
9086
|
-
const LATITUDE_PATCH_COUNT
|
|
9317
|
+
const LATITUDE_PATCH_COUNT = 20;
|
|
9087
9318
|
/**
|
|
9088
|
-
* Number of longitude segments used by the continuous Octopus 3D
|
|
9319
|
+
* Number of longitude segments used by the continuous Octopus 3D 4 mesh.
|
|
9089
9320
|
*
|
|
9090
|
-
* @private helper of `
|
|
9321
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9091
9322
|
*/
|
|
9092
|
-
const LONGITUDE_PATCH_COUNT
|
|
9323
|
+
const LONGITUDE_PATCH_COUNT = 48;
|
|
9093
9324
|
/**
|
|
9094
|
-
* Resolves visible projected patches for the continuous
|
|
9325
|
+
* Resolves visible projected patches for the continuous Octopus 3D 4 mesh.
|
|
9095
9326
|
*
|
|
9096
9327
|
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
9097
9328
|
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
9098
|
-
* than re-evaluated for every patch corner
|
|
9099
|
-
* for `latitudePatchCount * longitudePatchCount * 4` corners without caching, and each call
|
|
9100
|
-
* triggers an inner 8-iteration `Math.exp` loop, which dominates the per-frame cost.
|
|
9329
|
+
* than re-evaluated for every patch corner.
|
|
9101
9330
|
*
|
|
9102
|
-
* @private helper of `
|
|
9331
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9103
9332
|
*/
|
|
9104
|
-
function
|
|
9333
|
+
function resolveVisibleBlobbyContinuousPatches(options) {
|
|
9105
9334
|
const { center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette } = options;
|
|
9106
|
-
const latitudePatchCount = LATITUDE_PATCH_COUNT
|
|
9107
|
-
const longitudePatchCount = LONGITUDE_PATCH_COUNT
|
|
9335
|
+
const latitudePatchCount = LATITUDE_PATCH_COUNT;
|
|
9336
|
+
const longitudePatchCount = LONGITUDE_PATCH_COUNT;
|
|
9108
9337
|
const surfacePatches = [];
|
|
9109
9338
|
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
9110
9339
|
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
@@ -9121,13 +9350,13 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
9121
9350
|
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9122
9351
|
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9123
9352
|
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
9124
|
-
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] =
|
|
9125
|
-
cachedLobeWavesByCornerLongitude[boundaryIndex] =
|
|
9353
|
+
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
9354
|
+
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
9126
9355
|
}
|
|
9127
9356
|
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9128
9357
|
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
9129
|
-
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] =
|
|
9130
|
-
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] =
|
|
9358
|
+
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
9359
|
+
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
9131
9360
|
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
9132
9361
|
}
|
|
9133
9362
|
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
@@ -9137,7 +9366,7 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
9137
9366
|
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
9138
9367
|
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
9139
9368
|
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
9140
|
-
const cornerSample =
|
|
9369
|
+
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
9141
9370
|
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
9142
9371
|
}
|
|
9143
9372
|
}
|
|
@@ -9156,7 +9385,7 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
9156
9385
|
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
9157
9386
|
];
|
|
9158
9387
|
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
9159
|
-
if (surfaceNormal.z <= 0.
|
|
9388
|
+
if (surfaceNormal.z <= 0.006) {
|
|
9160
9389
|
continue;
|
|
9161
9390
|
}
|
|
9162
9391
|
const projectedCorners = [
|
|
@@ -9165,6 +9394,8 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
9165
9394
|
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
9166
9395
|
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
9167
9396
|
];
|
|
9397
|
+
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION$1), -1, 1);
|
|
9398
|
+
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
9168
9399
|
surfacePatches.push({
|
|
9169
9400
|
corners: projectedCorners,
|
|
9170
9401
|
averageDepth: (transformedCorners[0].z +
|
|
@@ -9172,99 +9403,108 @@ function resolveVisibleContinuousOctopusPatches(options) {
|
|
|
9172
9403
|
transformedCorners[2].z +
|
|
9173
9404
|
transformedCorners[3].z) /
|
|
9174
9405
|
4,
|
|
9175
|
-
lightIntensity
|
|
9176
|
-
|
|
9177
|
-
|
|
9406
|
+
lightIntensity,
|
|
9407
|
+
rimLightIntensity,
|
|
9408
|
+
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
9409
|
+
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
9178
9410
|
});
|
|
9179
9411
|
}
|
|
9180
9412
|
}
|
|
9181
9413
|
return surfacePatches;
|
|
9182
9414
|
}
|
|
9183
9415
|
/**
|
|
9184
|
-
* Samples one point on the continuous Octopus 3D
|
|
9185
|
-
*
|
|
9186
|
-
* The lower hemisphere is pulled into eight seeded waving lobes, so the portrait reads as
|
|
9187
|
-
* tentacled while still being rendered as one connected blobby mesh.
|
|
9416
|
+
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
9188
9417
|
*
|
|
9189
|
-
* @private helper of `
|
|
9418
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9190
9419
|
*/
|
|
9191
|
-
function
|
|
9192
|
-
return
|
|
9420
|
+
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
9421
|
+
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
9193
9422
|
}
|
|
9194
9423
|
/**
|
|
9195
|
-
* Samples one point on the continuous Octopus 3D
|
|
9196
|
-
* values to skip the per-call `Math.exp` tentacle-influence loop and the lobe-wave trig call.
|
|
9424
|
+
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
9197
9425
|
*
|
|
9198
|
-
* The patch loop quantizes the mesh into a fixed
|
|
9199
|
-
*
|
|
9200
|
-
*
|
|
9426
|
+
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
9427
|
+
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
9428
|
+
* of `latitudePatchCount * 4` times.
|
|
9201
9429
|
*
|
|
9202
|
-
* @private helper of `
|
|
9430
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9203
9431
|
*/
|
|
9204
|
-
function
|
|
9432
|
+
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
9205
9433
|
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
9206
9434
|
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
9207
9435
|
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
9208
|
-
const upperBlend = Math.pow(1 - verticalProgress, 1.
|
|
9209
|
-
const lowerBlend = smoothStep
|
|
9210
|
-
const tipBlend = smoothStep
|
|
9211
|
-
const centerPull = resolveSignedAngularDistance
|
|
9212
|
-
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.
|
|
9436
|
+
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
9437
|
+
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
9438
|
+
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
9439
|
+
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
9440
|
+
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
9213
9441
|
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
9214
9442
|
animationPhase * 0.6 +
|
|
9215
|
-
timeMs / (
|
|
9216
|
-
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.
|
|
9217
|
-
(0.
|
|
9218
|
-
const
|
|
9443
|
+
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
9444
|
+
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
9445
|
+
(0.32 + lowerBlend * 0.7);
|
|
9446
|
+
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
9219
9447
|
lowerBlend *
|
|
9220
9448
|
tentacleInfluence.core *
|
|
9221
9449
|
tentacleInfluence.swayScale;
|
|
9222
|
-
const
|
|
9450
|
+
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
9451
|
+
lowerBlend *
|
|
9452
|
+
tentacleInfluence.core *
|
|
9453
|
+
tentacleInfluence.swayScale *
|
|
9454
|
+
0.42;
|
|
9455
|
+
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
9456
|
+
tipBlend *
|
|
9457
|
+
tentacleInfluence.core *
|
|
9458
|
+
tentacleInfluence.curlScale *
|
|
9459
|
+
0.36;
|
|
9460
|
+
const horizontalScale = 1.06 +
|
|
9223
9461
|
mantleRipple +
|
|
9224
|
-
lowerBlend * (0.
|
|
9225
|
-
lowerBlend * tentacleInfluence.core * (0.
|
|
9462
|
+
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
9463
|
+
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
9226
9464
|
upperBlend * 0.08;
|
|
9227
|
-
const depthScale = 1.
|
|
9465
|
+
const depthScale = 1.08 +
|
|
9228
9466
|
upperBlend * 0.16 +
|
|
9229
|
-
Math.max(0, Math.cos(effectiveLongitude)) * 0.
|
|
9230
|
-
lowerBlend * tentacleInfluence.core * (0.
|
|
9467
|
+
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
9468
|
+
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
9231
9469
|
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
9232
|
-
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.
|
|
9470
|
+
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) * radiusX;
|
|
9233
9471
|
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
9234
|
-
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.
|
|
9472
|
+
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
9235
9473
|
const lowerDrop = lowerBlend *
|
|
9236
9474
|
radiusY *
|
|
9237
|
-
(0.
|
|
9475
|
+
(0.2 +
|
|
9238
9476
|
tentacleInfluence.core *
|
|
9239
|
-
(0.
|
|
9240
|
-
tentacleInfluence.lengthScale * 0.
|
|
9241
|
-
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.
|
|
9477
|
+
(0.42 +
|
|
9478
|
+
tentacleInfluence.lengthScale * 0.24 +
|
|
9479
|
+
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
9480
|
+
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
9242
9481
|
return {
|
|
9243
|
-
x: Math.sin(effectiveLongitude) * planarRadiusX +
|
|
9244
|
-
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.
|
|
9245
|
-
upperBlend * radiusY * 0.
|
|
9482
|
+
x: Math.sin(effectiveLongitude) * planarRadiusX + combinedTentacleSway + tentacleCurl * radiusX * 0.18,
|
|
9483
|
+
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
9484
|
+
upperBlend * radiusY * 0.12 +
|
|
9246
9485
|
lowerDrop +
|
|
9247
|
-
Math.sin(timeMs /
|
|
9248
|
-
Math.cos(timeMs /
|
|
9486
|
+
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
9487
|
+
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
9249
9488
|
lowerBlend *
|
|
9250
9489
|
tipBlend *
|
|
9251
9490
|
tentacleInfluence.core *
|
|
9252
9491
|
radiusY *
|
|
9253
|
-
0.
|
|
9492
|
+
0.04,
|
|
9254
9493
|
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
9255
|
-
Math.cos(timeMs /
|
|
9494
|
+
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
9256
9495
|
lowerBlend *
|
|
9257
9496
|
tentacleInfluence.core *
|
|
9258
9497
|
radiusZ *
|
|
9259
|
-
0.
|
|
9498
|
+
0.044 +
|
|
9499
|
+
tentacleCurl * radiusZ * 0.14,
|
|
9260
9500
|
};
|
|
9261
9501
|
}
|
|
9262
9502
|
/**
|
|
9263
9503
|
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
9264
9504
|
*
|
|
9265
|
-
* @private helper of `
|
|
9505
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9266
9506
|
*/
|
|
9267
|
-
function
|
|
9507
|
+
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
9268
9508
|
let totalWeight = 0;
|
|
9269
9509
|
let weightedSin = 0;
|
|
9270
9510
|
let weightedCos = 0;
|
|
@@ -9272,10 +9512,12 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
9272
9512
|
let weightedLengthScale = 0;
|
|
9273
9513
|
let weightedSwayScale = 0;
|
|
9274
9514
|
let weightedDepthScale = 0;
|
|
9275
|
-
let
|
|
9515
|
+
let weightedCurlScale = 0;
|
|
9516
|
+
let weightedPrimaryPhase = 0;
|
|
9517
|
+
let weightedSecondaryPhase = 0;
|
|
9276
9518
|
for (const tentacleProfile of options.tentacleProfiles) {
|
|
9277
|
-
const distance = Math.abs(resolveSignedAngularDistance
|
|
9278
|
-
const width = 0.
|
|
9519
|
+
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
9520
|
+
const width = 0.22 * tentacleProfile.widthScale;
|
|
9279
9521
|
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
9280
9522
|
totalWeight += weight;
|
|
9281
9523
|
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
@@ -9284,7 +9526,9 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
9284
9526
|
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
9285
9527
|
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
9286
9528
|
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
9287
|
-
|
|
9529
|
+
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
9530
|
+
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
9531
|
+
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
9288
9532
|
}
|
|
9289
9533
|
if (totalWeight < 0.0001) {
|
|
9290
9534
|
return {
|
|
@@ -9294,7 +9538,9 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
9294
9538
|
lengthScale: 1,
|
|
9295
9539
|
swayScale: 1,
|
|
9296
9540
|
depthScale: 1,
|
|
9297
|
-
|
|
9541
|
+
curlScale: 1,
|
|
9542
|
+
primaryPhase: 0,
|
|
9543
|
+
secondaryPhase: 0,
|
|
9298
9544
|
};
|
|
9299
9545
|
}
|
|
9300
9546
|
return {
|
|
@@ -9304,52 +9550,57 @@ function resolveContinuousTentacleInfluence(options, longitude) {
|
|
|
9304
9550
|
lengthScale: weightedLengthScale / totalWeight,
|
|
9305
9551
|
swayScale: weightedSwayScale / totalWeight,
|
|
9306
9552
|
depthScale: weightedDepthScale / totalWeight,
|
|
9307
|
-
|
|
9553
|
+
curlScale: weightedCurlScale / totalWeight,
|
|
9554
|
+
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
9555
|
+
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
9308
9556
|
};
|
|
9309
9557
|
}
|
|
9310
9558
|
/**
|
|
9311
9559
|
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
9312
9560
|
*
|
|
9313
|
-
* @private helper of `
|
|
9561
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9314
9562
|
*/
|
|
9315
|
-
function
|
|
9563
|
+
function resolveBlobbyLobeWave(options, longitude) {
|
|
9316
9564
|
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
9317
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT
|
|
9565
|
+
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
9318
9566
|
animationPhase +
|
|
9319
|
-
timeMs / (
|
|
9567
|
+
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
9320
9568
|
1) /
|
|
9321
9569
|
2);
|
|
9322
9570
|
}
|
|
9323
9571
|
/**
|
|
9324
9572
|
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
9325
9573
|
*
|
|
9326
|
-
* @private helper of `
|
|
9574
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9327
9575
|
*/
|
|
9328
|
-
function
|
|
9576
|
+
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
9329
9577
|
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
9330
|
-
if (tonalProgress < 0.
|
|
9578
|
+
if (tonalProgress < 0.12) {
|
|
9331
9579
|
return palette.highlight;
|
|
9332
9580
|
}
|
|
9333
|
-
if (tonalProgress < 0.
|
|
9581
|
+
if (tonalProgress < 0.3) {
|
|
9334
9582
|
return palette.secondary;
|
|
9335
9583
|
}
|
|
9336
9584
|
if (tonalProgress < 0.72) {
|
|
9337
|
-
return forwardness > 0.
|
|
9585
|
+
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
9338
9586
|
}
|
|
9339
9587
|
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
9340
9588
|
}
|
|
9341
9589
|
/**
|
|
9342
|
-
* Draws one projected mesh patch with
|
|
9590
|
+
* Draws one projected mesh patch with multi-pass shading and a soft edge.
|
|
9343
9591
|
*
|
|
9344
|
-
* @private helper of `
|
|
9592
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9345
9593
|
*/
|
|
9346
|
-
function
|
|
9594
|
+
function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
9347
9595
|
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
9348
9596
|
if (surfacePatch.lightIntensity > 0) {
|
|
9349
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.
|
|
9597
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.2 * surfacePatch.lightIntensity})`);
|
|
9350
9598
|
}
|
|
9351
9599
|
else if (surfacePatch.lightIntensity < 0) {
|
|
9352
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.
|
|
9600
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.26 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
9601
|
+
}
|
|
9602
|
+
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
9603
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
9353
9604
|
}
|
|
9354
9605
|
context.save();
|
|
9355
9606
|
context.beginPath();
|
|
@@ -9359,7 +9610,7 @@ function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
|
9359
9610
|
}
|
|
9360
9611
|
context.closePath();
|
|
9361
9612
|
context.strokeStyle = surfacePatch.outlineColor;
|
|
9362
|
-
context.lineWidth = Math.max(0.
|
|
9613
|
+
context.lineWidth = Math.max(0.6, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0026);
|
|
9363
9614
|
context.lineJoin = 'round';
|
|
9364
9615
|
context.stroke();
|
|
9365
9616
|
context.restore();
|
|
@@ -9367,9 +9618,9 @@ function drawContinuousSurfacePatch(context, surfacePatch) {
|
|
|
9367
9618
|
/**
|
|
9368
9619
|
* Draws projected mantle-current lines on the front of the mesh.
|
|
9369
9620
|
*
|
|
9370
|
-
* @private helper of `
|
|
9621
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9371
9622
|
*/
|
|
9372
|
-
function
|
|
9623
|
+
function drawBlobbyContinuousCurrents(options) {
|
|
9373
9624
|
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
9374
9625
|
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
9375
9626
|
const centerIndex = (currentCount - 1) / 2;
|
|
@@ -9379,11 +9630,11 @@ function drawProjectedSurfaceCurrents(options) {
|
|
|
9379
9630
|
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
9380
9631
|
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
9381
9632
|
const projectedPoints = [];
|
|
9382
|
-
for (let sampleIndex = 0; sampleIndex <
|
|
9383
|
-
const progress = sampleIndex /
|
|
9384
|
-
const latitude = -0.
|
|
9385
|
-
const longitude = baseLongitude + Math.sin(timeMs /
|
|
9386
|
-
const scenePoint = transformScenePoint(
|
|
9633
|
+
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
9634
|
+
const progress = sampleIndex / 8;
|
|
9635
|
+
const latitude = -0.48 + progress * 0.78;
|
|
9636
|
+
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
9637
|
+
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
9387
9638
|
if (scenePoint.z > center.z - size * 0.016) {
|
|
9388
9639
|
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
9389
9640
|
}
|
|
@@ -9405,38 +9656,61 @@ function drawProjectedSurfaceCurrents(options) {
|
|
|
9405
9656
|
/**
|
|
9406
9657
|
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
9407
9658
|
*
|
|
9408
|
-
* @private helper of `
|
|
9659
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9409
9660
|
*/
|
|
9410
|
-
function
|
|
9661
|
+
function drawBlobbyContinuousSuckers(options) {
|
|
9411
9662
|
const { surfaceOptions, size } = options;
|
|
9412
9663
|
const { timeMs } = surfaceOptions;
|
|
9413
9664
|
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
9414
|
-
if (Math.cos(tentacleProfile.centerLongitude) < -0.
|
|
9665
|
+
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
9415
9666
|
continue;
|
|
9416
9667
|
}
|
|
9417
|
-
for (let suckerIndex = 0; suckerIndex <
|
|
9418
|
-
const latitude = 0.
|
|
9419
|
-
const sideOffset = tentacleProfile.suckerSide * (0.
|
|
9420
|
-
const waveOffset = Math.sin(timeMs /
|
|
9421
|
-
|
|
9668
|
+
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
9669
|
+
const latitude = 0.5 + suckerIndex * 0.12;
|
|
9670
|
+
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
9671
|
+
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
9672
|
+
drawBlobbyContinuousSurfaceSpot({
|
|
9422
9673
|
...options,
|
|
9423
9674
|
latitude,
|
|
9424
9675
|
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
9425
|
-
radiusScale: size * (0.
|
|
9676
|
+
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
9426
9677
|
});
|
|
9427
9678
|
}
|
|
9428
9679
|
}
|
|
9429
9680
|
}
|
|
9430
9681
|
/**
|
|
9431
|
-
* Draws
|
|
9682
|
+
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
9432
9683
|
*
|
|
9433
|
-
* @private helper of `
|
|
9684
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9434
9685
|
*/
|
|
9435
|
-
function
|
|
9436
|
-
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette,
|
|
9437
|
-
const
|
|
9438
|
-
|
|
9439
|
-
|
|
9686
|
+
function drawBlobbySkinSpots(options) {
|
|
9687
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
9688
|
+
for (const skinSpot of skinSpots) {
|
|
9689
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
9690
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
9691
|
+
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
9692
|
+
continue;
|
|
9693
|
+
}
|
|
9694
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
9695
|
+
const spotRadius = size * skinSpot.radiusScale;
|
|
9696
|
+
context.save();
|
|
9697
|
+
context.beginPath();
|
|
9698
|
+
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
9699
|
+
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
9700
|
+
context.fill();
|
|
9701
|
+
context.restore();
|
|
9702
|
+
}
|
|
9703
|
+
}
|
|
9704
|
+
/**
|
|
9705
|
+
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
9706
|
+
*
|
|
9707
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9708
|
+
*/
|
|
9709
|
+
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
9710
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
9711
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
9712
|
+
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
9713
|
+
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
9440
9714
|
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
9441
9715
|
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
9442
9716
|
return;
|
|
@@ -9446,301 +9720,295 @@ function drawProjectedSurfaceSpot(options) {
|
|
|
9446
9720
|
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
9447
9721
|
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
9448
9722
|
radiusScale *
|
|
9449
|
-
0.
|
|
9723
|
+
0.78, size * 0.003, size * 0.02);
|
|
9450
9724
|
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
9451
9725
|
radiusScale *
|
|
9452
|
-
0.
|
|
9726
|
+
0.54, size * 0.0024, size * 0.015);
|
|
9453
9727
|
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
9454
9728
|
context.save();
|
|
9455
9729
|
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
9456
9730
|
context.rotate(rotation);
|
|
9457
9731
|
context.beginPath();
|
|
9458
9732
|
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
9459
|
-
context.fillStyle = `${palette.highlight}
|
|
9733
|
+
context.fillStyle = `${palette.highlight}80`;
|
|
9460
9734
|
context.fill();
|
|
9461
|
-
context.strokeStyle = `${palette.highlight}
|
|
9735
|
+
context.strokeStyle = `${palette.highlight}a8`;
|
|
9462
9736
|
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
9463
9737
|
context.stroke();
|
|
9464
9738
|
context.restore();
|
|
9465
9739
|
}
|
|
9740
|
+
/**
|
|
9741
|
+
* Draws a soft, slowly drifting gloss highlight on the front of the mesh.
|
|
9742
|
+
*
|
|
9743
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9744
|
+
*/
|
|
9745
|
+
function drawBlobbyContinuousGloss(options) {
|
|
9746
|
+
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size } = options;
|
|
9747
|
+
const { timeMs, animationPhase } = surfaceOptions;
|
|
9748
|
+
const glossLatitude = -0.3 + Math.sin(timeMs / 2700 + animationPhase) * 0.04;
|
|
9749
|
+
const glossLongitude = -0.18 + Math.cos(timeMs / 2300 + animationPhase * 0.8) * 0.05;
|
|
9750
|
+
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, glossLatitude, glossLongitude);
|
|
9751
|
+
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
9752
|
+
if (sceneCenterPoint.z <= center.z) {
|
|
9753
|
+
return;
|
|
9754
|
+
}
|
|
9755
|
+
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
9756
|
+
const glossRadius = size * 0.058;
|
|
9757
|
+
context.save();
|
|
9758
|
+
const glossGradient = context.createRadialGradient(projectedCenterPoint.x - glossRadius * 0.3, projectedCenterPoint.y - glossRadius * 0.4, glossRadius * 0.04, projectedCenterPoint.x, projectedCenterPoint.y, glossRadius);
|
|
9759
|
+
glossGradient.addColorStop(0, 'rgba(255, 255, 255, 0.34)');
|
|
9760
|
+
glossGradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.1)');
|
|
9761
|
+
glossGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
9762
|
+
context.fillStyle = glossGradient;
|
|
9763
|
+
context.beginPath();
|
|
9764
|
+
context.ellipse(projectedCenterPoint.x, projectedCenterPoint.y, glossRadius * 1.05, glossRadius * 0.78, 0, 0, Math.PI * 2);
|
|
9765
|
+
context.fill();
|
|
9766
|
+
context.restore();
|
|
9767
|
+
}
|
|
9466
9768
|
/**
|
|
9467
9769
|
* Resolves a signed angular distance from the source longitude to the target longitude.
|
|
9468
9770
|
*
|
|
9469
|
-
* @private helper of `
|
|
9771
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9470
9772
|
*/
|
|
9471
|
-
function resolveSignedAngularDistance
|
|
9773
|
+
function resolveSignedAngularDistance(sourceLongitude, targetLongitude) {
|
|
9472
9774
|
return Math.atan2(Math.sin(targetLongitude - sourceLongitude), Math.cos(targetLongitude - sourceLongitude));
|
|
9473
9775
|
}
|
|
9474
9776
|
/**
|
|
9475
9777
|
* Smoothly maps a value between two bounds into `[0, 1]`.
|
|
9476
9778
|
*
|
|
9477
|
-
* @private helper of `
|
|
9779
|
+
* @private helper of `octopus3d4AvatarVisual`
|
|
9478
9780
|
*/
|
|
9479
|
-
function smoothStep
|
|
9781
|
+
function smoothStep(edgeStart, edgeEnd, value) {
|
|
9480
9782
|
const progress = clampNumber$1((value - edgeStart) / (edgeEnd - edgeStart), 0, 1);
|
|
9481
9783
|
return progress * progress * (3 - 2 * progress);
|
|
9482
9784
|
}
|
|
9483
|
-
|
|
9484
|
-
/* eslint-disable no-magic-numbers */
|
|
9485
9785
|
/**
|
|
9486
|
-
*
|
|
9786
|
+
* Converts an opacity ratio into a two-digit hexadecimal alpha suffix.
|
|
9487
9787
|
*
|
|
9488
9788
|
* @private helper of `octopus3d4AvatarVisual`
|
|
9489
9789
|
*/
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
}
|
|
9790
|
+
function formatAlphaHex(opacity) {
|
|
9791
|
+
return Math.round(clampNumber$1(opacity, 0, 1) * 255)
|
|
9792
|
+
.toString(16)
|
|
9793
|
+
.padStart(2, '0');
|
|
9794
|
+
}
|
|
9795
|
+
|
|
9796
|
+
/* eslint-disable no-magic-numbers */
|
|
9495
9797
|
/**
|
|
9496
|
-
*
|
|
9798
|
+
* Light direction used by the organic 3D octopus shading.
|
|
9497
9799
|
*
|
|
9498
|
-
* @private helper of `
|
|
9800
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9499
9801
|
*/
|
|
9500
|
-
const
|
|
9501
|
-
x:
|
|
9502
|
-
y: -0.
|
|
9503
|
-
z: 0.
|
|
9802
|
+
const LIGHT_DIRECTION = normalizeVector3({
|
|
9803
|
+
x: 0.48,
|
|
9804
|
+
y: -0.62,
|
|
9805
|
+
z: 0.94,
|
|
9504
9806
|
});
|
|
9505
9807
|
/**
|
|
9506
|
-
*
|
|
9507
|
-
*
|
|
9508
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
9509
|
-
*/
|
|
9510
|
-
const OCTOPUS_TENTACLE_COUNT = 8;
|
|
9511
|
-
/**
|
|
9512
|
-
* Number of seeded skin spots painted across the octopus body.
|
|
9513
|
-
*
|
|
9514
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
9515
|
-
*/
|
|
9516
|
-
const SKIN_SPOT_COUNT = 14;
|
|
9517
|
-
/**
|
|
9518
|
-
* Cache keyed by the `createRandom` factory reference, stable per mounted `<Avatar/>` component.
|
|
9808
|
+
* Cache keyed by the `createRandom` factory reference (stable per mounted `<Avatar/>`).
|
|
9519
9809
|
*
|
|
9520
|
-
* @private helper of `
|
|
9810
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9521
9811
|
*/
|
|
9522
|
-
const
|
|
9812
|
+
const octopus3dStableStateCache = new WeakMap();
|
|
9523
9813
|
/**
|
|
9524
|
-
* Returns the stable per-avatar state, computing it on first access and caching
|
|
9814
|
+
* Returns the stable per-avatar state, computing it on first access and caching for subsequent frames.
|
|
9525
9815
|
*
|
|
9526
|
-
* @private helper of `
|
|
9816
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9527
9817
|
*/
|
|
9528
|
-
function
|
|
9529
|
-
const cached =
|
|
9818
|
+
function getOctopus3dStableState(createRandom) {
|
|
9819
|
+
const cached = octopus3dStableStateCache.get(createRandom);
|
|
9530
9820
|
if (cached !== undefined) {
|
|
9531
9821
|
return cached;
|
|
9532
9822
|
}
|
|
9533
|
-
const
|
|
9534
|
-
const
|
|
9535
|
-
const
|
|
9536
|
-
const
|
|
9537
|
-
const rightEyePhaseOffset = eyeRandom() * 0.7;
|
|
9823
|
+
const animationRandom = createRandom('octopus3d-animation-profile');
|
|
9824
|
+
const eyeRandom = createRandom('octopus3d-eye-profile');
|
|
9825
|
+
const leftEyePhaseOffset = eyeRandom() * 0.6;
|
|
9826
|
+
const rightEyePhaseOffset = eyeRandom() * 0.6;
|
|
9538
9827
|
const state = {
|
|
9539
|
-
morphologyProfile,
|
|
9828
|
+
morphologyProfile: createOctopus3MorphologyProfile(createRandom),
|
|
9540
9829
|
animationPhase: animationRandom() * Math.PI * 2,
|
|
9541
9830
|
leftEyePhaseOffset,
|
|
9542
9831
|
rightEyePhaseOffset,
|
|
9543
|
-
tentacleProfiles: createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile),
|
|
9544
|
-
skinSpots: createBlobbySkinSpots(createRandom),
|
|
9545
9832
|
};
|
|
9546
|
-
|
|
9833
|
+
octopus3dStableStateCache.set(createRandom, state);
|
|
9547
9834
|
return state;
|
|
9548
9835
|
}
|
|
9549
9836
|
/**
|
|
9550
|
-
*
|
|
9837
|
+
* Proper 3D Octopus visual built from projected organic meshes and tentacles.
|
|
9551
9838
|
*
|
|
9552
9839
|
* @private built-in avatar visual
|
|
9553
9840
|
*/
|
|
9554
|
-
const
|
|
9555
|
-
id: '
|
|
9556
|
-
title: 'Octopus 3D
|
|
9557
|
-
description: '
|
|
9841
|
+
const octopus3dAvatarVisual = {
|
|
9842
|
+
id: 'octopus3d',
|
|
9843
|
+
title: 'Octopus 3D',
|
|
9844
|
+
description: 'Proper 3D octopus portrait with a turning silhouette, expressive eyes, and depth-sorted tentacles.',
|
|
9558
9845
|
isAnimated: true,
|
|
9559
9846
|
supportsPointerTracking: true,
|
|
9560
9847
|
render({ context, size, palette, createRandom, timeMs, interaction }) {
|
|
9561
|
-
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset
|
|
9848
|
+
const { morphologyProfile, animationPhase, leftEyePhaseOffset, rightEyePhaseOffset } = getOctopus3dStableState(createRandom);
|
|
9562
9849
|
const sceneCenterX = size * 0.5;
|
|
9563
|
-
const sceneCenterY = size * 0.
|
|
9564
|
-
const bob = Math.sin(timeMs /
|
|
9565
|
-
const
|
|
9566
|
-
x: interaction.bodyOffsetX * size * 0.
|
|
9567
|
-
y: -size * 0.
|
|
9568
|
-
z: interaction.intensity * size * 0.
|
|
9850
|
+
const sceneCenterY = size * 0.56;
|
|
9851
|
+
const bob = Math.sin(timeMs / 920 + animationPhase) * size * 0.014;
|
|
9852
|
+
const mantleCenter = {
|
|
9853
|
+
x: interaction.bodyOffsetX * size * 0.042 + size * morphologyProfile.body.centerXJitterRatio * 0.65,
|
|
9854
|
+
y: -size * 0.09 + interaction.bodyOffsetY * size * 0.028 + bob,
|
|
9855
|
+
z: interaction.intensity * size * 0.012,
|
|
9569
9856
|
};
|
|
9570
|
-
const
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
const rotationX = -0.07 +
|
|
9575
|
-
Math.cos(timeMs / 3200 + animationPhase * 0.7) * 0.02 -
|
|
9576
|
-
interaction.bodyOffsetY * 0.08 -
|
|
9577
|
-
interaction.gazeY * 0.42;
|
|
9578
|
-
const surfaceOptions = {
|
|
9579
|
-
radiusX: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch * 1.12,
|
|
9580
|
-
radiusY: size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1,
|
|
9581
|
-
radiusZ: size *
|
|
9582
|
-
morphologyProfile.body.bodyRadiusRatio *
|
|
9583
|
-
(1.04 + (morphologyProfile.body.horizontalStretch - 1) * 0.2),
|
|
9584
|
-
morphologyProfile,
|
|
9585
|
-
timeMs,
|
|
9586
|
-
animationPhase,
|
|
9587
|
-
tentacleProfiles,
|
|
9857
|
+
const underbodyCenter = {
|
|
9858
|
+
x: mantleCenter.x * 0.86,
|
|
9859
|
+
y: mantleCenter.y + size * 0.168,
|
|
9860
|
+
z: mantleCenter.z - size * 0.018,
|
|
9588
9861
|
};
|
|
9589
|
-
const
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9862
|
+
const mantleRadiusX = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch;
|
|
9863
|
+
const mantleRadiusY = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1;
|
|
9864
|
+
const mantleRadiusZ = size *
|
|
9865
|
+
morphologyProfile.body.bodyRadiusRatio *
|
|
9866
|
+
(0.9 + (morphologyProfile.body.horizontalStretch - 1) * 0.3);
|
|
9867
|
+
const underbodyRadiusX = mantleRadiusX * (0.9 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.08);
|
|
9868
|
+
const underbodyRadiusY = mantleRadiusY * (0.44 + morphologyProfile.body.lowerDropRatio * 3.1);
|
|
9869
|
+
const underbodyRadiusZ = mantleRadiusZ * 0.78;
|
|
9870
|
+
const bodyYaw = -0.18 +
|
|
9871
|
+
Math.sin(timeMs / 2400 + animationPhase) * 0.05 +
|
|
9872
|
+
interaction.bodyOffsetX * 0.18 +
|
|
9873
|
+
interaction.gazeX * 0.22;
|
|
9874
|
+
const bodyPitch = -0.08 +
|
|
9875
|
+
Math.cos(timeMs / 2700 + animationPhase * 0.6) * 0.025 -
|
|
9876
|
+
interaction.bodyOffsetY * 0.08 -
|
|
9877
|
+
interaction.gazeY * 0.08;
|
|
9878
|
+
const headYaw = bodyYaw - 0.04 + interaction.gazeX * 0.56;
|
|
9879
|
+
const headPitch = bodyPitch - 0.02 - interaction.gazeY * 0.32;
|
|
9880
|
+
const mantlePatches = resolveVisibleEllipsoidPatches({
|
|
9881
|
+
center: mantleCenter,
|
|
9882
|
+
radiusX: mantleRadiusX,
|
|
9883
|
+
radiusY: mantleRadiusY,
|
|
9884
|
+
radiusZ: mantleRadiusZ,
|
|
9885
|
+
rotationX: headPitch,
|
|
9886
|
+
rotationY: headYaw,
|
|
9594
9887
|
sceneCenterX,
|
|
9595
9888
|
sceneCenterY,
|
|
9596
9889
|
size,
|
|
9597
9890
|
palette,
|
|
9891
|
+
verticalColorBias: 0,
|
|
9892
|
+
outlineColor: `${palette.highlight}7a`,
|
|
9598
9893
|
});
|
|
9599
|
-
const
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
const eyeRadiusY = eyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.92;
|
|
9607
|
-
drawAvatarFrame(context, size, palette);
|
|
9608
|
-
drawBlobbyContinuousAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
9609
|
-
drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs, morphologyProfile);
|
|
9610
|
-
for (const surfacePatch of surfacePatches.sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
9611
|
-
drawBlobbyContinuousSurfacePatch(context, surfacePatch);
|
|
9612
|
-
}
|
|
9613
|
-
drawBlobbySkinSpots({
|
|
9614
|
-
context,
|
|
9615
|
-
surfaceOptions,
|
|
9616
|
-
center: meshCenter,
|
|
9617
|
-
rotationX,
|
|
9618
|
-
rotationY,
|
|
9894
|
+
const underbodyPatches = resolveVisibleEllipsoidPatches({
|
|
9895
|
+
center: underbodyCenter,
|
|
9896
|
+
radiusX: underbodyRadiusX,
|
|
9897
|
+
radiusY: underbodyRadiusY,
|
|
9898
|
+
radiusZ: underbodyRadiusZ,
|
|
9899
|
+
rotationX: bodyPitch,
|
|
9900
|
+
rotationY: bodyYaw,
|
|
9619
9901
|
sceneCenterX,
|
|
9620
9902
|
sceneCenterY,
|
|
9621
9903
|
size,
|
|
9622
9904
|
palette,
|
|
9623
|
-
|
|
9905
|
+
verticalColorBias: 0.18,
|
|
9906
|
+
outlineColor: `${palette.shadow}8f`,
|
|
9624
9907
|
});
|
|
9625
|
-
|
|
9626
|
-
|
|
9627
|
-
surfaceOptions,
|
|
9628
|
-
center: meshCenter,
|
|
9629
|
-
rotationX,
|
|
9630
|
-
rotationY,
|
|
9631
|
-
sceneCenterX,
|
|
9632
|
-
sceneCenterY,
|
|
9633
|
-
size,
|
|
9634
|
-
palette,
|
|
9908
|
+
const tentacleStrokes = createOctopusTentacleStrokes({
|
|
9909
|
+
createRandom,
|
|
9635
9910
|
morphologyProfile,
|
|
9636
9911
|
timeMs,
|
|
9637
|
-
animationPhase,
|
|
9638
|
-
});
|
|
9639
|
-
drawBlobbyContinuousSuckers({
|
|
9640
|
-
context,
|
|
9641
|
-
surfaceOptions,
|
|
9642
|
-
center: meshCenter,
|
|
9643
|
-
rotationX,
|
|
9644
|
-
rotationY,
|
|
9645
|
-
sceneCenterX,
|
|
9646
|
-
sceneCenterY,
|
|
9647
9912
|
size,
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
rotationX,
|
|
9655
|
-
rotationY,
|
|
9913
|
+
center: underbodyCenter,
|
|
9914
|
+
radiusX: underbodyRadiusX,
|
|
9915
|
+
radiusY: underbodyRadiusY,
|
|
9916
|
+
radiusZ: underbodyRadiusZ,
|
|
9917
|
+
rotationX: bodyPitch,
|
|
9918
|
+
rotationY: bodyYaw,
|
|
9656
9919
|
sceneCenterX,
|
|
9657
9920
|
sceneCenterY,
|
|
9658
|
-
|
|
9659
|
-
palette,
|
|
9921
|
+
animationPhase,
|
|
9660
9922
|
});
|
|
9661
|
-
|
|
9662
|
-
|
|
9923
|
+
const faceEyeSpacing = size * morphologyProfile.face.eyeSpacingRatio * 0.92;
|
|
9924
|
+
const faceEyeYOffset = size * morphologyProfile.face.eyeCenterYOffsetRatio - mantleRadiusY * 0.02;
|
|
9925
|
+
const faceEyeRadiusX = size * morphologyProfile.face.eyeRadiusXRatio * 0.82;
|
|
9926
|
+
const faceEyeRadiusY = faceEyeRadiusX * morphologyProfile.face.eyeHeightRatio * 0.96;
|
|
9927
|
+
const mouthHalfWidth = size * morphologyProfile.face.mouthWidthRatio * 0.92;
|
|
9928
|
+
const mouthY = size * morphologyProfile.face.mouthYOffsetRatio + mantleRadiusY * 0.08;
|
|
9929
|
+
drawAvatarFrame(context, size, palette);
|
|
9930
|
+
drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs);
|
|
9931
|
+
drawOctopus3dShadow(context, size, palette, interaction, timeMs);
|
|
9932
|
+
for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => !candidateTentacleStroke.isFrontFacing)) {
|
|
9933
|
+
drawTentacleStroke(context, tentacleStroke, palette);
|
|
9934
|
+
}
|
|
9935
|
+
for (const surfacePatch of [...mantlePatches, ...underbodyPatches].sort((firstSurfacePatch, secondSurfacePatch) => firstSurfacePatch.averageDepth - secondSurfacePatch.averageDepth)) {
|
|
9936
|
+
drawSurfacePatch(context, surfacePatch);
|
|
9937
|
+
}
|
|
9938
|
+
for (const tentacleStroke of tentacleStrokes.filter((candidateTentacleStroke) => candidateTentacleStroke.isFrontFacing)) {
|
|
9939
|
+
drawTentacleStroke(context, tentacleStroke, palette);
|
|
9940
|
+
}
|
|
9941
|
+
drawProjectedOrganicEye(context, {
|
|
9942
|
+
x: -faceEyeSpacing,
|
|
9943
|
+
y: faceEyeYOffset,
|
|
9944
|
+
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -faceEyeSpacing, faceEyeYOffset),
|
|
9945
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + leftEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9946
|
+
drawProjectedOrganicEye(context, {
|
|
9947
|
+
x: faceEyeSpacing,
|
|
9948
|
+
y: faceEyeYOffset,
|
|
9949
|
+
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
|
|
9950
|
+
}, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + rightEyePhaseOffset, interaction, morphologyProfile.face.eyeStyle);
|
|
9663
9951
|
drawProjectedOrganicMouth(context, [
|
|
9664
|
-
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9952
|
+
{
|
|
9953
|
+
x: -mouthHalfWidth,
|
|
9954
|
+
y: mouthY,
|
|
9955
|
+
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -mouthHalfWidth, mouthY),
|
|
9956
|
+
},
|
|
9957
|
+
{
|
|
9958
|
+
x: size * morphologyProfile.face.mouthCenterOffsetRatio,
|
|
9959
|
+
y: mouthY +
|
|
9960
|
+
size * morphologyProfile.face.mouthCurveDepthRatio * 0.38 +
|
|
9961
|
+
Math.sin(timeMs / 760 + animationPhase) * size * 0.01 +
|
|
9962
|
+
interaction.gazeY * size * 0.01,
|
|
9963
|
+
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, size * morphologyProfile.face.mouthCenterOffsetRatio, mouthY),
|
|
9964
|
+
},
|
|
9965
|
+
{
|
|
9966
|
+
x: mouthHalfWidth,
|
|
9967
|
+
y: mouthY,
|
|
9968
|
+
z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY),
|
|
9969
|
+
},
|
|
9970
|
+
], mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, palette, size);
|
|
9668
9971
|
},
|
|
9669
9972
|
};
|
|
9670
9973
|
/**
|
|
9671
|
-
*
|
|
9672
|
-
*
|
|
9673
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
9674
|
-
*/
|
|
9675
|
-
function createBlobbyContinuousTentacleProfiles(createRandom, morphologyProfile) {
|
|
9676
|
-
return Array.from({ length: OCTOPUS_TENTACLE_COUNT }, (_, tentacleIndex) => {
|
|
9677
|
-
const tentacleRandom = createRandom(`octopus3d4-tentacle-${tentacleIndex}`);
|
|
9678
|
-
const progress = tentacleIndex / (OCTOPUS_TENTACLE_COUNT - 1);
|
|
9679
|
-
return {
|
|
9680
|
-
centerLongitude: -Math.PI * 0.9 +
|
|
9681
|
-
progress * Math.PI * 1.8 +
|
|
9682
|
-
(tentacleRandom() - 0.5) * (0.06 + morphologyProfile.tentacles.rootSpreadScale * 0.025),
|
|
9683
|
-
widthScale: 0.92 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.baseWidthScale - 1) * 0.18,
|
|
9684
|
-
lengthScale: 0.9 + tentacleRandom() * 0.34 + (morphologyProfile.tentacles.flowLengthScale - 1) * 0.24,
|
|
9685
|
-
swayScale: 0.86 + tentacleRandom() * 0.4 + (morphologyProfile.tentacles.swayScale - 1) * 0.22,
|
|
9686
|
-
depthScale: 0.9 + tentacleRandom() * 0.3 + (morphologyProfile.tentacles.tipReachScale - 1) * 0.22,
|
|
9687
|
-
curlScale: 0.62 + tentacleRandom() * 0.46,
|
|
9688
|
-
primaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9689
|
-
secondaryPhase: tentacleRandom() * Math.PI * 2,
|
|
9690
|
-
suckerSide: tentacleRandom() > 0.5 ? 1 : -1,
|
|
9691
|
-
};
|
|
9692
|
-
});
|
|
9693
|
-
}
|
|
9694
|
-
/**
|
|
9695
|
-
* Creates seeded skin pigment spots distributed across the upper octopus mesh.
|
|
9696
|
-
*
|
|
9697
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
9698
|
-
*/
|
|
9699
|
-
function createBlobbySkinSpots(createRandom) {
|
|
9700
|
-
const spotRandom = createRandom('octopus3d4-skin-spots');
|
|
9701
|
-
return Array.from({ length: SKIN_SPOT_COUNT }, () => ({
|
|
9702
|
-
latitude: -0.45 + spotRandom() * 0.7,
|
|
9703
|
-
longitude: -0.5 + spotRandom() * 1.0,
|
|
9704
|
-
radiusScale: 0.0028 + spotRandom() * 0.0052,
|
|
9705
|
-
opacity: 0.16 + spotRandom() * 0.22,
|
|
9706
|
-
}));
|
|
9707
|
-
}
|
|
9708
|
-
/**
|
|
9709
|
-
* Draws the soft underwater atmosphere behind the continuous octopus mesh.
|
|
9974
|
+
* Draws the atmospheric underwater glow behind the octopus mesh.
|
|
9710
9975
|
*
|
|
9711
|
-
* @private helper of `
|
|
9976
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9712
9977
|
*/
|
|
9713
|
-
function
|
|
9714
|
-
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.
|
|
9715
|
-
glowGradient.addColorStop(0, `${palette.highlight}
|
|
9716
|
-
glowGradient.addColorStop(0.
|
|
9978
|
+
function drawOctopus3dAtmosphere(context, size, palette, sceneCenterX, sceneCenterY, interaction, timeMs) {
|
|
9979
|
+
const glowGradient = context.createRadialGradient(sceneCenterX + interaction.gazeX * size * 0.1, sceneCenterY - size * 0.2 + interaction.gazeY * size * 0.05, size * 0.04, sceneCenterX, sceneCenterY - size * 0.04, size * 0.62);
|
|
9980
|
+
glowGradient.addColorStop(0, `${palette.highlight}5c`);
|
|
9981
|
+
glowGradient.addColorStop(0.36, `${palette.accent}24`);
|
|
9717
9982
|
glowGradient.addColorStop(1, `${palette.highlight}00`);
|
|
9718
9983
|
context.fillStyle = glowGradient;
|
|
9719
9984
|
context.fillRect(0, 0, size, size);
|
|
9720
|
-
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs /
|
|
9721
|
-
lowerGradient.addColorStop(0, `${palette.secondary}
|
|
9985
|
+
const lowerGradient = context.createRadialGradient(sceneCenterX + Math.sin(timeMs / 1700) * size * 0.05, sceneCenterY + size * 0.23, size * 0.08, sceneCenterX, sceneCenterY + size * 0.28, size * 0.5);
|
|
9986
|
+
lowerGradient.addColorStop(0, `${palette.secondary}1d`);
|
|
9722
9987
|
lowerGradient.addColorStop(1, `${palette.secondary}00`);
|
|
9723
9988
|
context.fillStyle = lowerGradient;
|
|
9724
9989
|
context.fillRect(0, 0, size, size);
|
|
9725
9990
|
}
|
|
9726
9991
|
/**
|
|
9727
|
-
* Draws the soft
|
|
9992
|
+
* Draws the soft ground shadow below the octopus.
|
|
9728
9993
|
*
|
|
9729
|
-
*
|
|
9994
|
+
* Uses a scaled radial gradient instead of `context.filter = 'blur()'` to approximate the
|
|
9995
|
+
* blurry ellipse without triggering a costly software rasterization pass on every frame.
|
|
9996
|
+
*
|
|
9997
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9730
9998
|
*/
|
|
9731
|
-
function
|
|
9732
|
-
const cx = size * 0.5 + interaction.gazeX * size * 0.
|
|
9733
|
-
const cy = size * 0.
|
|
9734
|
-
const rx = size * (0.
|
|
9735
|
-
const ry = size * 0.
|
|
9999
|
+
function drawOctopus3dShadow(context, size, palette, interaction, timeMs) {
|
|
10000
|
+
const cx = size * 0.5 + interaction.gazeX * size * 0.04;
|
|
10001
|
+
const cy = size * 0.87 + Math.sin(timeMs / 920) * size * 0.008;
|
|
10002
|
+
const rx = size * (0.18 + interaction.intensity * 0.02);
|
|
10003
|
+
const ry = size * 0.06;
|
|
9736
10004
|
context.save();
|
|
9737
10005
|
context.translate(cx, cy);
|
|
9738
10006
|
context.scale(1, ry / rx);
|
|
9739
|
-
const blurRadius = rx * 1.
|
|
10007
|
+
const blurRadius = rx * 1.4;
|
|
9740
10008
|
const shadowGradient = context.createRadialGradient(0, 0, 0, 0, 0, blurRadius);
|
|
9741
|
-
shadowGradient.addColorStop(0, `${palette.shadow}
|
|
9742
|
-
shadowGradient.addColorStop(0.45, `${palette.shadow}
|
|
9743
|
-
shadowGradient.addColorStop(0.8, `${palette.shadow}
|
|
10009
|
+
shadowGradient.addColorStop(0, `${palette.shadow}7a`);
|
|
10010
|
+
shadowGradient.addColorStop(0.45, `${palette.shadow}44`);
|
|
10011
|
+
shadowGradient.addColorStop(0.8, `${palette.shadow}1a`);
|
|
9744
10012
|
shadowGradient.addColorStop(1, `${palette.shadow}00`);
|
|
9745
10013
|
context.fillStyle = shadowGradient;
|
|
9746
10014
|
context.beginPath();
|
|
@@ -9749,297 +10017,89 @@ function drawBlobbyContinuousShadow(context, size, palette, interaction, timeMs,
|
|
|
9749
10017
|
context.restore();
|
|
9750
10018
|
}
|
|
9751
10019
|
/**
|
|
9752
|
-
*
|
|
10020
|
+
* Resolves visible projected patches for one rotated ellipsoid mesh.
|
|
9753
10021
|
*
|
|
9754
|
-
* @private helper of `
|
|
10022
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9755
10023
|
*/
|
|
9756
|
-
|
|
10024
|
+
function resolveVisibleEllipsoidPatches(options) {
|
|
10025
|
+
const { center, radiusX, radiusY, radiusZ, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, verticalColorBias, outlineColor, } = options;
|
|
10026
|
+
const latitudePatchCount = 10;
|
|
10027
|
+
const longitudePatchCount = 18;
|
|
10028
|
+
const surfacePatches = [];
|
|
10029
|
+
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
10030
|
+
const startLatitude = -Math.PI / 2 + (latitudeIndex / latitudePatchCount) * Math.PI;
|
|
10031
|
+
const endLatitude = -Math.PI / 2 + ((latitudeIndex + 1) / latitudePatchCount) * Math.PI;
|
|
10032
|
+
const verticalProgress = (latitudeIndex + 0.5) / latitudePatchCount;
|
|
10033
|
+
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
10034
|
+
const startLongitude = -Math.PI + (longitudeIndex / longitudePatchCount) * Math.PI * 2;
|
|
10035
|
+
const endLongitude = -Math.PI + ((longitudeIndex + 1) / longitudePatchCount) * Math.PI * 2;
|
|
10036
|
+
const localCorners = [
|
|
10037
|
+
sampleEllipsoidPoint(radiusX, radiusY, radiusZ, startLatitude, startLongitude),
|
|
10038
|
+
sampleEllipsoidPoint(radiusX, radiusY, radiusZ, startLatitude, endLongitude),
|
|
10039
|
+
sampleEllipsoidPoint(radiusX, radiusY, radiusZ, endLatitude, endLongitude),
|
|
10040
|
+
sampleEllipsoidPoint(radiusX, radiusY, radiusZ, endLatitude, startLongitude),
|
|
10041
|
+
];
|
|
10042
|
+
const transformedCorners = localCorners.map((localCorner) => transformScenePoint(localCorner, center, rotationX, rotationY));
|
|
10043
|
+
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
10044
|
+
if (surfaceNormal.z <= 0.01) {
|
|
10045
|
+
continue;
|
|
10046
|
+
}
|
|
10047
|
+
const projectedCorners = transformedCorners.map((transformedCorner) => projectScenePoint(transformedCorner, size, sceneCenterX, sceneCenterY));
|
|
10048
|
+
surfacePatches.push({
|
|
10049
|
+
corners: projectedCorners,
|
|
10050
|
+
averageDepth: transformedCorners.reduce((depthSum, transformedCorner) => depthSum + transformedCorner.z, 0) /
|
|
10051
|
+
transformedCorners.length,
|
|
10052
|
+
lightIntensity: clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1),
|
|
10053
|
+
fillStyle: resolveSurfacePatchFillStyle(palette, verticalProgress + verticalColorBias),
|
|
10054
|
+
outlineColor,
|
|
10055
|
+
});
|
|
10056
|
+
}
|
|
10057
|
+
}
|
|
10058
|
+
return surfacePatches;
|
|
10059
|
+
}
|
|
9757
10060
|
/**
|
|
9758
|
-
*
|
|
10061
|
+
* Samples one point on an ellipsoid aligned to the local axes.
|
|
9759
10062
|
*
|
|
9760
|
-
* @private helper of `
|
|
10063
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9761
10064
|
*/
|
|
9762
|
-
|
|
10065
|
+
function sampleEllipsoidPoint(radiusX, radiusY, radiusZ, latitude, longitude) {
|
|
10066
|
+
const cosineLatitude = Math.cos(latitude);
|
|
10067
|
+
return {
|
|
10068
|
+
x: Math.sin(longitude) * cosineLatitude * radiusX,
|
|
10069
|
+
y: Math.sin(latitude) * radiusY,
|
|
10070
|
+
z: Math.cos(longitude) * cosineLatitude * radiusZ,
|
|
10071
|
+
};
|
|
10072
|
+
}
|
|
9763
10073
|
/**
|
|
9764
|
-
* Resolves
|
|
9765
|
-
*
|
|
9766
|
-
* Within a single frame, mesh corner samples and longitude-only computations (tentacle
|
|
9767
|
-
* influence and lobe wave) are quantized to the patch grid and computed once each rather
|
|
9768
|
-
* than re-evaluated for every patch corner.
|
|
10074
|
+
* Resolves one base fill tone for a surface patch across the octopus body.
|
|
9769
10075
|
*
|
|
9770
|
-
* @private helper of `
|
|
10076
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
9771
10077
|
*/
|
|
9772
|
-
function
|
|
9773
|
-
const
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
const surfacePatches = [];
|
|
9777
|
-
const latitudeBoundaries = new Float64Array(latitudePatchCount + 1);
|
|
9778
|
-
const longitudeBoundaries = new Float64Array(longitudePatchCount + 1);
|
|
9779
|
-
for (let boundaryIndex = 0; boundaryIndex <= latitudePatchCount; boundaryIndex++) {
|
|
9780
|
-
latitudeBoundaries[boundaryIndex] = -Math.PI / 2 + (boundaryIndex / latitudePatchCount) * Math.PI;
|
|
10078
|
+
function resolveSurfacePatchFillStyle(palette, verticalProgress) {
|
|
10079
|
+
const clampedVerticalProgress = clampNumber$1(verticalProgress, 0, 1);
|
|
10080
|
+
if (clampedVerticalProgress < 0.2) {
|
|
10081
|
+
return palette.highlight;
|
|
9781
10082
|
}
|
|
9782
|
-
|
|
9783
|
-
|
|
10083
|
+
if (clampedVerticalProgress < 0.45) {
|
|
10084
|
+
return palette.secondary;
|
|
9784
10085
|
}
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
const cachedTentacleInfluencesByPatchCenterLongitude = new Array(longitudePatchCount);
|
|
9788
|
-
const cachedLobeWavesByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9789
|
-
const cachedCosByPatchCenterLongitude = new Float64Array(longitudePatchCount);
|
|
9790
|
-
for (let boundaryIndex = 0; boundaryIndex <= longitudePatchCount; boundaryIndex++) {
|
|
9791
|
-
const cornerLongitude = longitudeBoundaries[boundaryIndex];
|
|
9792
|
-
cachedTentacleInfluencesByCornerLongitude[boundaryIndex] = resolveBlobbyTentacleInfluence(options, cornerLongitude);
|
|
9793
|
-
cachedLobeWavesByCornerLongitude[boundaryIndex] = resolveBlobbyLobeWave(options, cornerLongitude);
|
|
10086
|
+
if (clampedVerticalProgress < 0.8) {
|
|
10087
|
+
return palette.primary;
|
|
9794
10088
|
}
|
|
9795
|
-
|
|
9796
|
-
const patchCenterLongitude = (longitudeBoundaries[longitudeIndex] + longitudeBoundaries[longitudeIndex + 1]) / 2;
|
|
9797
|
-
cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyTentacleInfluence(options, patchCenterLongitude);
|
|
9798
|
-
cachedLobeWavesByPatchCenterLongitude[longitudeIndex] = resolveBlobbyLobeWave(options, patchCenterLongitude);
|
|
9799
|
-
cachedCosByPatchCenterLongitude[longitudeIndex] = Math.max(0, Math.cos(patchCenterLongitude));
|
|
9800
|
-
}
|
|
9801
|
-
const cornerCount = (latitudePatchCount + 1) * (longitudePatchCount + 1);
|
|
9802
|
-
const transformedCornerSamples = new Array(cornerCount);
|
|
9803
|
-
for (let latitudeBoundaryIndex = 0; latitudeBoundaryIndex <= latitudePatchCount; latitudeBoundaryIndex++) {
|
|
9804
|
-
const cornerLatitude = latitudeBoundaries[latitudeBoundaryIndex];
|
|
9805
|
-
for (let longitudeBoundaryIndex = 0; longitudeBoundaryIndex <= longitudePatchCount; longitudeBoundaryIndex++) {
|
|
9806
|
-
const cornerLongitude = longitudeBoundaries[longitudeBoundaryIndex];
|
|
9807
|
-
const cornerIndex = latitudeBoundaryIndex * (longitudePatchCount + 1) + longitudeBoundaryIndex;
|
|
9808
|
-
const cornerSample = sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, cornerLatitude, cornerLongitude, cachedTentacleInfluencesByCornerLongitude[longitudeBoundaryIndex], cachedLobeWavesByCornerLongitude[longitudeBoundaryIndex]);
|
|
9809
|
-
transformedCornerSamples[cornerIndex] = transformScenePoint(cornerSample, center, rotationX, rotationY);
|
|
9810
|
-
}
|
|
9811
|
-
}
|
|
9812
|
-
for (let latitudeIndex = 0; latitudeIndex < latitudePatchCount; latitudeIndex++) {
|
|
9813
|
-
const startLatitude = latitudeBoundaries[latitudeIndex];
|
|
9814
|
-
const endLatitude = latitudeBoundaries[latitudeIndex + 1];
|
|
9815
|
-
const centerLatitude = (startLatitude + endLatitude) / 2;
|
|
9816
|
-
const verticalProgress = (Math.sin(centerLatitude) + 1) / 2;
|
|
9817
|
-
const startCornerRowOffset = latitudeIndex * (longitudePatchCount + 1);
|
|
9818
|
-
const endCornerRowOffset = (latitudeIndex + 1) * (longitudePatchCount + 1);
|
|
9819
|
-
for (let longitudeIndex = 0; longitudeIndex < longitudePatchCount; longitudeIndex++) {
|
|
9820
|
-
const transformedCorners = [
|
|
9821
|
-
transformedCornerSamples[startCornerRowOffset + longitudeIndex],
|
|
9822
|
-
transformedCornerSamples[startCornerRowOffset + longitudeIndex + 1],
|
|
9823
|
-
transformedCornerSamples[endCornerRowOffset + longitudeIndex + 1],
|
|
9824
|
-
transformedCornerSamples[endCornerRowOffset + longitudeIndex],
|
|
9825
|
-
];
|
|
9826
|
-
const surfaceNormal = normalizeVector3(crossProduct3D(subtractPoint3D(transformedCorners[1], transformedCorners[0]), subtractPoint3D(transformedCorners[2], transformedCorners[0])));
|
|
9827
|
-
if (surfaceNormal.z <= 0.006) {
|
|
9828
|
-
continue;
|
|
9829
|
-
}
|
|
9830
|
-
const projectedCorners = [
|
|
9831
|
-
projectScenePoint(transformedCorners[0], size, sceneCenterX, sceneCenterY),
|
|
9832
|
-
projectScenePoint(transformedCorners[1], size, sceneCenterX, sceneCenterY),
|
|
9833
|
-
projectScenePoint(transformedCorners[2], size, sceneCenterX, sceneCenterY),
|
|
9834
|
-
projectScenePoint(transformedCorners[3], size, sceneCenterX, sceneCenterY),
|
|
9835
|
-
];
|
|
9836
|
-
const lightIntensity = clampNumber$1(dotProduct3D(surfaceNormal, LIGHT_DIRECTION), -1, 1);
|
|
9837
|
-
const rimLightIntensity = Math.pow(clampNumber$1(dotProduct3D(surfaceNormal, RIM_LIGHT_DIRECTION), 0, 1), 2.6);
|
|
9838
|
-
surfacePatches.push({
|
|
9839
|
-
corners: projectedCorners,
|
|
9840
|
-
averageDepth: (transformedCorners[0].z +
|
|
9841
|
-
transformedCorners[1].z +
|
|
9842
|
-
transformedCorners[2].z +
|
|
9843
|
-
transformedCorners[3].z) /
|
|
9844
|
-
4,
|
|
9845
|
-
lightIntensity,
|
|
9846
|
-
rimLightIntensity,
|
|
9847
|
-
fillStyle: resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, cachedCosByPatchCenterLongitude[longitudeIndex], cachedTentacleInfluencesByPatchCenterLongitude[longitudeIndex].core, cachedLobeWavesByPatchCenterLongitude[longitudeIndex]),
|
|
9848
|
-
outlineColor: verticalProgress < 0.52 ? `${palette.highlight}5c` : `${palette.shadow}66`,
|
|
9849
|
-
});
|
|
9850
|
-
}
|
|
9851
|
-
}
|
|
9852
|
-
return surfacePatches;
|
|
9853
|
-
}
|
|
9854
|
-
/**
|
|
9855
|
-
* Samples one point on the continuous Octopus 3D 4 surface.
|
|
9856
|
-
*
|
|
9857
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
9858
|
-
*/
|
|
9859
|
-
function sampleBlobbyContinuousSurfacePoint(options, latitude, longitude) {
|
|
9860
|
-
return sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, resolveBlobbyTentacleInfluence(options, longitude), resolveBlobbyLobeWave(options, longitude));
|
|
9861
|
-
}
|
|
9862
|
-
/**
|
|
9863
|
-
* Samples one point on the continuous Octopus 3D 4 surface using precomputed longitude-only values.
|
|
9864
|
-
*
|
|
9865
|
-
* The patch loop quantizes the mesh into a fixed corner grid, so the same longitude is reused
|
|
9866
|
-
* across every latitude row and each tentacle/lobe value is computed once per frame instead
|
|
9867
|
-
* of `latitudePatchCount * 4` times.
|
|
9868
|
-
*
|
|
9869
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
9870
|
-
*/
|
|
9871
|
-
function sampleBlobbyContinuousSurfacePointWithLongitudeCache(options, latitude, longitude, tentacleInfluence, lowerLobeWave) {
|
|
9872
|
-
const { radiusX, radiusY, radiusZ, morphologyProfile, timeMs, animationPhase } = options;
|
|
9873
|
-
const cosineLatitude = Math.max(0, Math.cos(latitude));
|
|
9874
|
-
const verticalProgress = (Math.sin(latitude) + 1) / 2;
|
|
9875
|
-
const upperBlend = Math.pow(1 - verticalProgress, 1.32);
|
|
9876
|
-
const lowerBlend = smoothStep(0.34, 1, verticalProgress);
|
|
9877
|
-
const tipBlend = smoothStep(0.66, 1, verticalProgress);
|
|
9878
|
-
const centerPull = resolveSignedAngularDistance(longitude, tentacleInfluence.centerLongitude);
|
|
9879
|
-
const effectiveLongitude = longitude + centerPull * lowerBlend * tentacleInfluence.core * (0.26 + tipBlend * 0.22);
|
|
9880
|
-
const mantleRipple = Math.sin(longitude * morphologyProfile.body.lobeCount +
|
|
9881
|
-
animationPhase * 0.6 +
|
|
9882
|
-
timeMs / (1700 + morphologyProfile.body.lobeCount * 28)) *
|
|
9883
|
-
(0.018 + morphologyProfile.body.wobbleAmplitudeRatio * 0.78) *
|
|
9884
|
-
(0.32 + lowerBlend * 0.7);
|
|
9885
|
-
const primaryTentacleWave = Math.sin(timeMs / 740 + tentacleInfluence.primaryPhase + verticalProgress * 2.6) *
|
|
9886
|
-
lowerBlend *
|
|
9887
|
-
tentacleInfluence.core *
|
|
9888
|
-
tentacleInfluence.swayScale;
|
|
9889
|
-
const secondaryTentacleWave = Math.sin(timeMs / 470 + tentacleInfluence.secondaryPhase + verticalProgress * 4.2) *
|
|
9890
|
-
lowerBlend *
|
|
9891
|
-
tentacleInfluence.core *
|
|
9892
|
-
tentacleInfluence.swayScale *
|
|
9893
|
-
0.42;
|
|
9894
|
-
const tentacleCurl = Math.sin(timeMs / 1180 + tentacleInfluence.primaryPhase * 0.6 + verticalProgress * 3.2) *
|
|
9895
|
-
tipBlend *
|
|
9896
|
-
tentacleInfluence.core *
|
|
9897
|
-
tentacleInfluence.curlScale *
|
|
9898
|
-
0.36;
|
|
9899
|
-
const horizontalScale = 1.06 +
|
|
9900
|
-
mantleRipple +
|
|
9901
|
-
lowerBlend * (0.18 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.12) +
|
|
9902
|
-
lowerBlend * tentacleInfluence.core * (0.22 + lowerLobeWave * 0.14) -
|
|
9903
|
-
upperBlend * 0.08;
|
|
9904
|
-
const depthScale = 1.08 +
|
|
9905
|
-
upperBlend * 0.16 +
|
|
9906
|
-
Math.max(0, Math.cos(effectiveLongitude)) * 0.12 +
|
|
9907
|
-
lowerBlend * tentacleInfluence.core * (0.12 + tentacleInfluence.depthScale * 0.07) -
|
|
9908
|
-
Math.max(0, -Math.cos(effectiveLongitude)) * 0.05;
|
|
9909
|
-
const tentacleTubeRadius = lowerBlend * tentacleInfluence.core * (0.12 + tipBlend * 0.07 + tentacleInfluence.widthScale * 0.028) * radiusX;
|
|
9910
|
-
const planarRadiusX = cosineLatitude * radiusX * horizontalScale + tentacleTubeRadius;
|
|
9911
|
-
const planarRadiusZ = cosineLatitude * radiusZ * depthScale + tentacleTubeRadius * 0.74;
|
|
9912
|
-
const lowerDrop = lowerBlend *
|
|
9913
|
-
radiusY *
|
|
9914
|
-
(0.2 +
|
|
9915
|
-
tentacleInfluence.core *
|
|
9916
|
-
(0.42 +
|
|
9917
|
-
tentacleInfluence.lengthScale * 0.24 +
|
|
9918
|
-
(morphologyProfile.tentacles.flowLengthScale - 1) * 0.1));
|
|
9919
|
-
const combinedTentacleSway = (primaryTentacleWave + secondaryTentacleWave) * radiusX * (0.06 + tipBlend * 0.06);
|
|
9920
|
-
return {
|
|
9921
|
-
x: Math.sin(effectiveLongitude) * planarRadiusX + combinedTentacleSway + tentacleCurl * radiusX * 0.18,
|
|
9922
|
-
y: Math.sin(latitude) * radiusY * (1 + upperBlend * 0.14) -
|
|
9923
|
-
upperBlend * radiusY * 0.12 +
|
|
9924
|
-
lowerDrop +
|
|
9925
|
-
Math.sin(timeMs / 1380 + animationPhase + latitude * 1.5) * lowerBlend * radiusY * 0.022 +
|
|
9926
|
-
Math.cos(timeMs / 820 + tentacleInfluence.primaryPhase) *
|
|
9927
|
-
lowerBlend *
|
|
9928
|
-
tipBlend *
|
|
9929
|
-
tentacleInfluence.core *
|
|
9930
|
-
radiusY *
|
|
9931
|
-
0.04,
|
|
9932
|
-
z: Math.cos(effectiveLongitude) * planarRadiusZ +
|
|
9933
|
-
Math.cos(timeMs / 960 + tentacleInfluence.primaryPhase + verticalProgress) *
|
|
9934
|
-
lowerBlend *
|
|
9935
|
-
tentacleInfluence.core *
|
|
9936
|
-
radiusZ *
|
|
9937
|
-
0.044 +
|
|
9938
|
-
tentacleCurl * radiusZ * 0.14,
|
|
9939
|
-
};
|
|
9940
|
-
}
|
|
9941
|
-
/**
|
|
9942
|
-
* Blends nearby seeded tentacle profiles at one mesh longitude.
|
|
9943
|
-
*
|
|
9944
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
9945
|
-
*/
|
|
9946
|
-
function resolveBlobbyTentacleInfluence(options, longitude) {
|
|
9947
|
-
let totalWeight = 0;
|
|
9948
|
-
let weightedSin = 0;
|
|
9949
|
-
let weightedCos = 0;
|
|
9950
|
-
let weightedWidthScale = 0;
|
|
9951
|
-
let weightedLengthScale = 0;
|
|
9952
|
-
let weightedSwayScale = 0;
|
|
9953
|
-
let weightedDepthScale = 0;
|
|
9954
|
-
let weightedCurlScale = 0;
|
|
9955
|
-
let weightedPrimaryPhase = 0;
|
|
9956
|
-
let weightedSecondaryPhase = 0;
|
|
9957
|
-
for (const tentacleProfile of options.tentacleProfiles) {
|
|
9958
|
-
const distance = Math.abs(resolveSignedAngularDistance(longitude, tentacleProfile.centerLongitude));
|
|
9959
|
-
const width = 0.22 * tentacleProfile.widthScale;
|
|
9960
|
-
const weight = Math.exp(-(distance * distance) / (width * width));
|
|
9961
|
-
totalWeight += weight;
|
|
9962
|
-
weightedSin += Math.sin(tentacleProfile.centerLongitude) * weight;
|
|
9963
|
-
weightedCos += Math.cos(tentacleProfile.centerLongitude) * weight;
|
|
9964
|
-
weightedWidthScale += tentacleProfile.widthScale * weight;
|
|
9965
|
-
weightedLengthScale += tentacleProfile.lengthScale * weight;
|
|
9966
|
-
weightedSwayScale += tentacleProfile.swayScale * weight;
|
|
9967
|
-
weightedDepthScale += tentacleProfile.depthScale * weight;
|
|
9968
|
-
weightedCurlScale += tentacleProfile.curlScale * weight;
|
|
9969
|
-
weightedPrimaryPhase += tentacleProfile.primaryPhase * weight;
|
|
9970
|
-
weightedSecondaryPhase += tentacleProfile.secondaryPhase * weight;
|
|
9971
|
-
}
|
|
9972
|
-
if (totalWeight < 0.0001) {
|
|
9973
|
-
return {
|
|
9974
|
-
core: 0,
|
|
9975
|
-
centerLongitude: longitude,
|
|
9976
|
-
widthScale: 1,
|
|
9977
|
-
lengthScale: 1,
|
|
9978
|
-
swayScale: 1,
|
|
9979
|
-
depthScale: 1,
|
|
9980
|
-
curlScale: 1,
|
|
9981
|
-
primaryPhase: 0,
|
|
9982
|
-
secondaryPhase: 0,
|
|
9983
|
-
};
|
|
9984
|
-
}
|
|
9985
|
-
return {
|
|
9986
|
-
core: clampNumber$1(totalWeight, 0, 1),
|
|
9987
|
-
centerLongitude: Math.atan2(weightedSin / totalWeight, weightedCos / totalWeight),
|
|
9988
|
-
widthScale: weightedWidthScale / totalWeight,
|
|
9989
|
-
lengthScale: weightedLengthScale / totalWeight,
|
|
9990
|
-
swayScale: weightedSwayScale / totalWeight,
|
|
9991
|
-
depthScale: weightedDepthScale / totalWeight,
|
|
9992
|
-
curlScale: weightedCurlScale / totalWeight,
|
|
9993
|
-
primaryPhase: weightedPrimaryPhase / totalWeight,
|
|
9994
|
-
secondaryPhase: weightedSecondaryPhase / totalWeight,
|
|
9995
|
-
};
|
|
9996
|
-
}
|
|
9997
|
-
/**
|
|
9998
|
-
* Resolves the soft lower wave that makes the continuous mesh read as a set of tentacles.
|
|
9999
|
-
*
|
|
10000
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
10001
|
-
*/
|
|
10002
|
-
function resolveBlobbyLobeWave(options, longitude) {
|
|
10003
|
-
const { morphologyProfile, animationPhase, timeMs } = options;
|
|
10004
|
-
return ((Math.cos(longitude * OCTOPUS_TENTACLE_COUNT +
|
|
10005
|
-
animationPhase +
|
|
10006
|
-
timeMs / (940 + morphologyProfile.body.lobeCount * 18)) +
|
|
10007
|
-
1) /
|
|
10008
|
-
2);
|
|
10009
|
-
}
|
|
10010
|
-
/**
|
|
10011
|
-
* Resolves one base fill tone for a patch on the continuous octopus mesh.
|
|
10012
|
-
*
|
|
10013
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
10014
|
-
*/
|
|
10015
|
-
function resolveBlobbySurfacePatchFillStyle(palette, verticalProgress, forwardness, tentacleCore, lowerLobeWave) {
|
|
10016
|
-
const tonalProgress = clampNumber$1(verticalProgress + lowerLobeWave * 0.1 + tentacleCore * 0.08 - forwardness * 0.08, 0, 1);
|
|
10017
|
-
if (tonalProgress < 0.12) {
|
|
10018
|
-
return palette.highlight;
|
|
10019
|
-
}
|
|
10020
|
-
if (tonalProgress < 0.3) {
|
|
10021
|
-
return palette.secondary;
|
|
10022
|
-
}
|
|
10023
|
-
if (tonalProgress < 0.72) {
|
|
10024
|
-
return forwardness > 0.56 ? palette.secondary : palette.primary;
|
|
10025
|
-
}
|
|
10026
|
-
return tentacleCore > 0.44 ? `${palette.primary}f4` : `${palette.shadow}ee`;
|
|
10089
|
+
return `${palette.shadow}f2`;
|
|
10027
10090
|
}
|
|
10028
10091
|
/**
|
|
10029
|
-
* Draws one projected mesh patch with
|
|
10092
|
+
* Draws one projected mesh patch with organic shading.
|
|
10030
10093
|
*
|
|
10031
|
-
* @private helper of `
|
|
10094
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
10032
10095
|
*/
|
|
10033
|
-
function
|
|
10096
|
+
function drawSurfacePatch(context, surfacePatch) {
|
|
10034
10097
|
drawProjectedQuad(context, surfacePatch.corners, surfacePatch.fillStyle);
|
|
10035
10098
|
if (surfacePatch.lightIntensity > 0) {
|
|
10036
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.
|
|
10099
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 255, 255, ${0.15 * surfacePatch.lightIntensity})`);
|
|
10037
10100
|
}
|
|
10038
10101
|
else if (surfacePatch.lightIntensity < 0) {
|
|
10039
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.
|
|
10040
|
-
}
|
|
10041
|
-
if (surfacePatch.rimLightIntensity > 0.04) {
|
|
10042
|
-
drawProjectedQuad(context, surfacePatch.corners, `rgba(255, 247, 230, ${0.22 * surfacePatch.rimLightIntensity})`);
|
|
10102
|
+
drawProjectedQuad(context, surfacePatch.corners, `rgba(0, 0, 0, ${0.24 * Math.abs(surfacePatch.lightIntensity)})`);
|
|
10043
10103
|
}
|
|
10044
10104
|
context.save();
|
|
10045
10105
|
context.beginPath();
|
|
@@ -10049,187 +10109,127 @@ function drawBlobbyContinuousSurfacePatch(context, surfacePatch) {
|
|
|
10049
10109
|
}
|
|
10050
10110
|
context.closePath();
|
|
10051
10111
|
context.strokeStyle = surfacePatch.outlineColor;
|
|
10052
|
-
context.lineWidth = Math.max(
|
|
10053
|
-
context.lineJoin = 'round';
|
|
10054
|
-
context.stroke();
|
|
10055
|
-
context.restore();
|
|
10056
|
-
}
|
|
10057
|
-
/**
|
|
10058
|
-
* Draws projected mantle-current lines on the front of the mesh.
|
|
10059
|
-
*
|
|
10060
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
10061
|
-
*/
|
|
10062
|
-
function drawBlobbyContinuousCurrents(options) {
|
|
10063
|
-
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, morphologyProfile, timeMs, animationPhase, } = options;
|
|
10064
|
-
const currentCount = Math.min(6, morphologyProfile.details.mantleCurrentCount);
|
|
10065
|
-
const centerIndex = (currentCount - 1) / 2;
|
|
10066
|
-
context.save();
|
|
10067
|
-
context.lineCap = 'round';
|
|
10112
|
+
context.lineWidth = Math.max(1, getProjectedQuadPerimeter(surfacePatch.corners) * 0.0044);
|
|
10068
10113
|
context.lineJoin = 'round';
|
|
10069
|
-
for (let currentIndex = 0; currentIndex < currentCount; currentIndex++) {
|
|
10070
|
-
const baseLongitude = (currentIndex - centerIndex) * 0.15;
|
|
10071
|
-
const projectedPoints = [];
|
|
10072
|
-
for (let sampleIndex = 0; sampleIndex < 9; sampleIndex++) {
|
|
10073
|
-
const progress = sampleIndex / 8;
|
|
10074
|
-
const latitude = -0.48 + progress * 0.78;
|
|
10075
|
-
const longitude = baseLongitude + Math.sin(timeMs / 1140 + animationPhase + currentIndex * 0.7 + progress * 2) * 0.038;
|
|
10076
|
-
const scenePoint = transformScenePoint(sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude), center, rotationX, rotationY);
|
|
10077
|
-
if (scenePoint.z > center.z - size * 0.016) {
|
|
10078
|
-
projectedPoints.push(projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
10079
|
-
}
|
|
10080
|
-
}
|
|
10081
|
-
if (projectedPoints.length < 3) {
|
|
10082
|
-
continue;
|
|
10083
|
-
}
|
|
10084
|
-
context.beginPath();
|
|
10085
|
-
context.moveTo(projectedPoints[0].x, projectedPoints[0].y);
|
|
10086
|
-
for (const projectedPoint of projectedPoints.slice(1)) {
|
|
10087
|
-
context.lineTo(projectedPoint.x, projectedPoint.y);
|
|
10088
|
-
}
|
|
10089
|
-
context.strokeStyle = currentIndex % 2 === 0 ? `${palette.highlight}3d` : `${palette.accent}33`;
|
|
10090
|
-
context.lineWidth = size * (0.0055 + currentIndex * 0.00045);
|
|
10091
|
-
context.stroke();
|
|
10092
|
-
}
|
|
10093
|
-
context.restore();
|
|
10094
|
-
}
|
|
10095
|
-
/**
|
|
10096
|
-
* Draws small projected sucker highlights on the waving lower mesh lobes.
|
|
10097
|
-
*
|
|
10098
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
10099
|
-
*/
|
|
10100
|
-
function drawBlobbyContinuousSuckers(options) {
|
|
10101
|
-
const { surfaceOptions, size } = options;
|
|
10102
|
-
const { timeMs } = surfaceOptions;
|
|
10103
|
-
for (const tentacleProfile of surfaceOptions.tentacleProfiles) {
|
|
10104
|
-
if (Math.cos(tentacleProfile.centerLongitude) < -0.16) {
|
|
10105
|
-
continue;
|
|
10106
|
-
}
|
|
10107
|
-
for (let suckerIndex = 0; suckerIndex < 4; suckerIndex++) {
|
|
10108
|
-
const latitude = 0.5 + suckerIndex * 0.12;
|
|
10109
|
-
const sideOffset = tentacleProfile.suckerSide * (0.038 + suckerIndex * 0.014) * tentacleProfile.widthScale;
|
|
10110
|
-
const waveOffset = Math.sin(timeMs / 880 + tentacleProfile.primaryPhase + suckerIndex * 0.78) * 0.02;
|
|
10111
|
-
drawBlobbyContinuousSurfaceSpot({
|
|
10112
|
-
...options,
|
|
10113
|
-
latitude,
|
|
10114
|
-
longitude: tentacleProfile.centerLongitude + sideOffset + waveOffset,
|
|
10115
|
-
radiusScale: size * (0.0068 - suckerIndex * 0.0006),
|
|
10116
|
-
});
|
|
10117
|
-
}
|
|
10118
|
-
}
|
|
10119
|
-
}
|
|
10120
|
-
/**
|
|
10121
|
-
* Draws seeded pigment spots across the upper mesh for a richer skin texture.
|
|
10122
|
-
*
|
|
10123
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
10124
|
-
*/
|
|
10125
|
-
function drawBlobbySkinSpots(options) {
|
|
10126
|
-
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, skinSpots, } = options;
|
|
10127
|
-
for (const skinSpot of skinSpots) {
|
|
10128
|
-
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, skinSpot.latitude, skinSpot.longitude);
|
|
10129
|
-
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10130
|
-
if (sceneCenterPoint.z <= center.z - size * 0.01) {
|
|
10131
|
-
continue;
|
|
10132
|
-
}
|
|
10133
|
-
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10134
|
-
const spotRadius = size * skinSpot.radiusScale;
|
|
10135
|
-
context.save();
|
|
10136
|
-
context.beginPath();
|
|
10137
|
-
context.arc(projectedCenterPoint.x, projectedCenterPoint.y, spotRadius, 0, Math.PI * 2);
|
|
10138
|
-
context.fillStyle = `${palette.shadow}${formatAlphaHex(skinSpot.opacity)}`;
|
|
10139
|
-
context.fill();
|
|
10140
|
-
context.restore();
|
|
10141
|
-
}
|
|
10142
|
-
}
|
|
10143
|
-
/**
|
|
10144
|
-
* Draws one tiny projected surface spot by sampling local mesh tangents.
|
|
10145
|
-
*
|
|
10146
|
-
* @private helper of `octopus3d4AvatarVisual`
|
|
10147
|
-
*/
|
|
10148
|
-
function drawBlobbyContinuousSurfaceSpot(options) {
|
|
10149
|
-
const { context, surfaceOptions, center, rotationX, rotationY, sceneCenterX, sceneCenterY, size, palette, latitude, longitude, radiusScale, } = options;
|
|
10150
|
-
const localCenter = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude);
|
|
10151
|
-
const localHorizontal = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude, longitude + 0.018);
|
|
10152
|
-
const localVertical = sampleBlobbyContinuousSurfacePoint(surfaceOptions, latitude + 0.018, longitude);
|
|
10153
|
-
const sceneCenterPoint = transformScenePoint(localCenter, center, rotationX, rotationY);
|
|
10154
|
-
if (sceneCenterPoint.z <= center.z - size * 0.012) {
|
|
10155
|
-
return;
|
|
10156
|
-
}
|
|
10157
|
-
const projectedCenterPoint = projectScenePoint(sceneCenterPoint, size, sceneCenterX, sceneCenterY);
|
|
10158
|
-
const projectedHorizontalPoint = projectScenePoint(transformScenePoint(localHorizontal, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
10159
|
-
const projectedVerticalPoint = projectScenePoint(transformScenePoint(localVertical, center, rotationX, rotationY), size, sceneCenterX, sceneCenterY);
|
|
10160
|
-
const horizontalRadius = clampNumber$1(Math.hypot(projectedHorizontalPoint.x - projectedCenterPoint.x, projectedHorizontalPoint.y - projectedCenterPoint.y) *
|
|
10161
|
-
radiusScale *
|
|
10162
|
-
0.78, size * 0.003, size * 0.02);
|
|
10163
|
-
const verticalRadius = clampNumber$1(Math.hypot(projectedVerticalPoint.x - projectedCenterPoint.x, projectedVerticalPoint.y - projectedCenterPoint.y) *
|
|
10164
|
-
radiusScale *
|
|
10165
|
-
0.54, size * 0.0024, size * 0.015);
|
|
10166
|
-
const rotation = Math.atan2(projectedHorizontalPoint.y - projectedCenterPoint.y, projectedHorizontalPoint.x - projectedCenterPoint.x);
|
|
10167
|
-
context.save();
|
|
10168
|
-
context.translate(projectedCenterPoint.x, projectedCenterPoint.y);
|
|
10169
|
-
context.rotate(rotation);
|
|
10170
|
-
context.beginPath();
|
|
10171
|
-
context.ellipse(0, 0, horizontalRadius, verticalRadius, 0, 0, Math.PI * 2);
|
|
10172
|
-
context.fillStyle = `${palette.highlight}80`;
|
|
10173
|
-
context.fill();
|
|
10174
|
-
context.strokeStyle = `${palette.highlight}a8`;
|
|
10175
|
-
context.lineWidth = Math.max(0.7, size * 0.0028);
|
|
10176
10114
|
context.stroke();
|
|
10177
10115
|
context.restore();
|
|
10178
10116
|
}
|
|
10179
10117
|
/**
|
|
10180
|
-
*
|
|
10118
|
+
* Creates the projected 3D tentacle strokes orbiting around the lower octopus body.
|
|
10181
10119
|
*
|
|
10182
|
-
* @private helper of `
|
|
10120
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
10183
10121
|
*/
|
|
10184
|
-
function
|
|
10185
|
-
const {
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10122
|
+
function createOctopusTentacleStrokes(options) {
|
|
10123
|
+
const { createRandom, morphologyProfile, timeMs, size, center, radiusX, radiusY, radiusZ, rotationX, rotationY, sceneCenterX, sceneCenterY, animationPhase, } = options;
|
|
10124
|
+
return Array.from({ length: morphologyProfile.tentacles.count }, (_, tentacleIndex) => {
|
|
10125
|
+
const tentacleRandom = createRandom(`octopus3d-tentacle-${tentacleIndex}`);
|
|
10126
|
+
const spreadProgress = morphologyProfile.tentacles.count === 1 ? 0.5 : tentacleIndex / (morphologyProfile.tentacles.count - 1);
|
|
10127
|
+
const orbitAngle = -Math.PI * 0.92 + spreadProgress * Math.PI * 1.84 + (tentacleRandom() - 0.5) * 0.16;
|
|
10128
|
+
const flowLength = size * (0.19 + morphologyProfile.tentacles.flowLengthScale * 0.075 + tentacleRandom() * 0.018);
|
|
10129
|
+
const lateralReach = size *
|
|
10130
|
+
(0.08 + morphologyProfile.tentacles.lateralReachScale * 0.05 + Math.abs(Math.sin(orbitAngle)) * 0.018);
|
|
10131
|
+
const depthReach = size * (0.028 + morphologyProfile.tentacles.tipReachScale * 0.032);
|
|
10132
|
+
const sway = Math.sin(timeMs / (760 + tentacleIndex * 36) + animationPhase + tentacleRandom() * Math.PI * 2);
|
|
10133
|
+
const anchorPoint = {
|
|
10134
|
+
x: Math.sin(orbitAngle) * radiusX * (0.84 + tentacleRandom() * 0.08),
|
|
10135
|
+
y: radiusY * (0.22 + tentacleRandom() * 0.18),
|
|
10136
|
+
z: Math.cos(orbitAngle) * radiusZ * (0.72 + tentacleRandom() * 0.12),
|
|
10137
|
+
};
|
|
10138
|
+
const controlPointOne = {
|
|
10139
|
+
x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * 0.44,
|
|
10140
|
+
y: anchorPoint.y + flowLength * 0.26,
|
|
10141
|
+
z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.3 + sway * size * 0.012,
|
|
10142
|
+
};
|
|
10143
|
+
const controlPointTwo = {
|
|
10144
|
+
x: anchorPoint.x +
|
|
10145
|
+
Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
|
|
10146
|
+
y: anchorPoint.y + flowLength * 0.66,
|
|
10147
|
+
z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.72 + sway * size * 0.02,
|
|
10148
|
+
};
|
|
10149
|
+
const endPoint = {
|
|
10150
|
+
x: anchorPoint.x +
|
|
10151
|
+
Math.sin(orbitAngle) * lateralReach * (1.02 + morphologyProfile.tentacles.tipWidthScale * 0.12) +
|
|
10152
|
+
sway * size * 0.028,
|
|
10153
|
+
y: anchorPoint.y + flowLength,
|
|
10154
|
+
z: anchorPoint.z + Math.cos(orbitAngle) * depthReach + sway * size * 0.016,
|
|
10155
|
+
};
|
|
10156
|
+
const scenePoints = Array.from({ length: 12 }, (_, sampleIndex) => transformScenePoint(sampleCubicBezierPoint3D(anchorPoint, controlPointOne, controlPointTwo, endPoint, sampleIndex / 11), center, rotationX, rotationY));
|
|
10157
|
+
const projectedPoints = scenePoints.map((scenePoint) => projectScenePoint(scenePoint, size, sceneCenterX, sceneCenterY));
|
|
10158
|
+
const averageDepth = scenePoints.reduce((depthSum, scenePoint) => depthSum + scenePoint.z, 0) / scenePoints.length;
|
|
10159
|
+
return {
|
|
10160
|
+
projectedPoints,
|
|
10161
|
+
averageDepth,
|
|
10162
|
+
isFrontFacing: averageDepth >= center.z - size * 0.006,
|
|
10163
|
+
baseWidth: size *
|
|
10164
|
+
(0.019 +
|
|
10165
|
+
morphologyProfile.tentacles.baseWidthScale * 0.007 +
|
|
10166
|
+
tentacleRandom() * 0.003 +
|
|
10167
|
+
Math.abs(Math.sin(orbitAngle)) * 0.002),
|
|
10168
|
+
tipWidth: size * (0.0046 + morphologyProfile.tentacles.tipWidthScale * 0.0018),
|
|
10169
|
+
colorBias: tentacleRandom(),
|
|
10170
|
+
};
|
|
10171
|
+
});
|
|
10206
10172
|
}
|
|
10207
10173
|
/**
|
|
10208
|
-
*
|
|
10174
|
+
* Samples one point on a cubic Bezier curve in 3D.
|
|
10209
10175
|
*
|
|
10210
|
-
* @private helper of `
|
|
10176
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
10211
10177
|
*/
|
|
10212
|
-
function
|
|
10213
|
-
|
|
10178
|
+
function sampleCubicBezierPoint3D(startPoint, controlPointOne, controlPointTwo, endPoint, progress) {
|
|
10179
|
+
const inverseProgress = 1 - progress;
|
|
10180
|
+
return {
|
|
10181
|
+
x: inverseProgress * inverseProgress * inverseProgress * startPoint.x +
|
|
10182
|
+
3 * inverseProgress * inverseProgress * progress * controlPointOne.x +
|
|
10183
|
+
3 * inverseProgress * progress * progress * controlPointTwo.x +
|
|
10184
|
+
progress * progress * progress * endPoint.x,
|
|
10185
|
+
y: inverseProgress * inverseProgress * inverseProgress * startPoint.y +
|
|
10186
|
+
3 * inverseProgress * inverseProgress * progress * controlPointOne.y +
|
|
10187
|
+
3 * inverseProgress * progress * progress * controlPointTwo.y +
|
|
10188
|
+
progress * progress * progress * endPoint.y,
|
|
10189
|
+
z: inverseProgress * inverseProgress * inverseProgress * startPoint.z +
|
|
10190
|
+
3 * inverseProgress * inverseProgress * progress * controlPointOne.z +
|
|
10191
|
+
3 * inverseProgress * progress * progress * controlPointTwo.z +
|
|
10192
|
+
progress * progress * progress * endPoint.z,
|
|
10193
|
+
};
|
|
10214
10194
|
}
|
|
10215
10195
|
/**
|
|
10216
|
-
*
|
|
10196
|
+
* Draws one projected tentacle stroke with a slim highlight ridge.
|
|
10217
10197
|
*
|
|
10218
|
-
* @private helper of `
|
|
10198
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
10219
10199
|
*/
|
|
10220
|
-
function
|
|
10221
|
-
const
|
|
10222
|
-
|
|
10200
|
+
function drawTentacleStroke(context, tentacleStroke, palette) {
|
|
10201
|
+
const projectedSegments = tentacleStroke.projectedPoints.length - 1;
|
|
10202
|
+
for (let segmentIndex = 0; segmentIndex < projectedSegments; segmentIndex++) {
|
|
10203
|
+
const startPoint = tentacleStroke.projectedPoints[segmentIndex];
|
|
10204
|
+
const endPoint = tentacleStroke.projectedPoints[segmentIndex + 1];
|
|
10205
|
+
const progress = segmentIndex / projectedSegments;
|
|
10206
|
+
const width = tentacleStroke.baseWidth + (tentacleStroke.tipWidth - tentacleStroke.baseWidth) * progress;
|
|
10207
|
+
context.beginPath();
|
|
10208
|
+
context.moveTo(startPoint.x, startPoint.y);
|
|
10209
|
+
context.lineTo(endPoint.x, endPoint.y);
|
|
10210
|
+
context.strokeStyle = tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
|
|
10211
|
+
context.lineWidth = width;
|
|
10212
|
+
context.lineCap = 'round';
|
|
10213
|
+
context.stroke();
|
|
10214
|
+
context.beginPath();
|
|
10215
|
+
context.moveTo(startPoint.x, startPoint.y);
|
|
10216
|
+
context.lineTo(endPoint.x, endPoint.y);
|
|
10217
|
+
context.strokeStyle = tentacleStroke.isFrontFacing ? `${palette.highlight}80` : `${palette.highlight}40`;
|
|
10218
|
+
context.lineWidth = Math.max(1, width * 0.34);
|
|
10219
|
+
context.lineCap = 'round';
|
|
10220
|
+
context.stroke();
|
|
10221
|
+
}
|
|
10223
10222
|
}
|
|
10224
10223
|
/**
|
|
10225
|
-
*
|
|
10224
|
+
* Resolves the front surface depth on an ellipsoid for one local face point.
|
|
10226
10225
|
*
|
|
10227
|
-
* @private helper of `
|
|
10226
|
+
* @private helper of `octopus3dAvatarVisual`
|
|
10228
10227
|
*/
|
|
10229
|
-
function
|
|
10230
|
-
|
|
10231
|
-
|
|
10232
|
-
|
|
10228
|
+
function resolveEllipsoidSurfaceDepth(radiusX, radiusY, radiusZ, x, y) {
|
|
10229
|
+
const normalizedX = x / radiusX;
|
|
10230
|
+
const normalizedY = y / radiusY;
|
|
10231
|
+
const remainingDepthRatio = Math.max(0, 1 - normalizedX * normalizedX - normalizedY * normalizedY);
|
|
10232
|
+
return Math.sqrt(remainingDepthRatio) * radiusZ;
|
|
10233
10233
|
}
|
|
10234
10234
|
|
|
10235
10235
|
/* eslint-disable no-magic-numbers */
|
|
@@ -11052,14 +11052,14 @@ function resolveAvatarVisualId(value) {
|
|
|
11052
11052
|
/**
|
|
11053
11053
|
* META AVATAR commitment definition
|
|
11054
11054
|
*
|
|
11055
|
-
* The `META AVATAR`
|
|
11056
|
-
* the agent does not provide an explicit `META IMAGE`.
|
|
11055
|
+
* The `META AVATAR` and `META VISUAL` commitments set the built-in default avatar visual
|
|
11056
|
+
* used when the agent does not provide an explicit `META IMAGE`.
|
|
11057
11057
|
*
|
|
11058
11058
|
* @private [🪔] Maybe export the commitments through some package
|
|
11059
11059
|
*/
|
|
11060
11060
|
class MetaAvatarCommitmentDefinition extends BaseCommitmentDefinition {
|
|
11061
|
-
constructor() {
|
|
11062
|
-
super(
|
|
11061
|
+
constructor(type = 'META AVATAR') {
|
|
11062
|
+
super(type);
|
|
11063
11063
|
}
|
|
11064
11064
|
/**
|
|
11065
11065
|
* Short one-line description of META AVATAR.
|
|
@@ -11079,14 +11079,15 @@ class MetaAvatarCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
11079
11079
|
get documentation() {
|
|
11080
11080
|
const supportedVisuals = AVATAR_VISUALS.map((avatarVisual) => `\`${avatarVisual.id}\``).join(', ');
|
|
11081
11081
|
return spaceTrim$1(`
|
|
11082
|
-
#
|
|
11082
|
+
# ${this.type}
|
|
11083
11083
|
|
|
11084
11084
|
Sets the built-in avatar visual used for the agent when no explicit \`META IMAGE\` is provided.
|
|
11085
|
+
\`META AVATAR\` and \`META VISUAL\` are equivalent.
|
|
11085
11086
|
|
|
11086
11087
|
## Key aspects
|
|
11087
11088
|
|
|
11088
11089
|
- Does not modify the agent's behavior or responses.
|
|
11089
|
-
- Only one \`META AVATAR\` should be used per agent.
|
|
11090
|
+
- Only one \`META AVATAR\` or \`META VISUAL\` should be used per agent.
|
|
11090
11091
|
- If multiple are specified, the last one takes precedence.
|
|
11091
11092
|
- Values are matched case-insensitively and spaces, hyphens, and underscores are normalized.
|
|
11092
11093
|
- Supported visuals are derived from the shared avatar registry: ${supportedVisuals}.
|
|
@@ -11101,15 +11102,15 @@ class MetaAvatarCommitmentDefinition extends BaseCommitmentDefinition {
|
|
|
11101
11102
|
\`\`\`
|
|
11102
11103
|
|
|
11103
11104
|
\`\`\`book
|
|
11104
|
-
|
|
11105
|
+
Minecraft Assistant
|
|
11105
11106
|
|
|
11106
|
-
META
|
|
11107
|
+
META VISUAL Minecraft2
|
|
11107
11108
|
GOAL Answer in a calm and focused way.
|
|
11108
11109
|
\`\`\`
|
|
11109
11110
|
`);
|
|
11110
11111
|
}
|
|
11111
11112
|
applyToAgentModelRequirements(requirements, content) {
|
|
11112
|
-
// META AVATAR
|
|
11113
|
+
// META AVATAR and META VISUAL don't modify the system message or model requirements.
|
|
11113
11114
|
// It's handled separately in the parsing logic for profile avatar resolution.
|
|
11114
11115
|
return requirements;
|
|
11115
11116
|
}
|
|
@@ -20628,6 +20629,7 @@ const COMMITMENT_REGISTRY = [
|
|
|
20628
20629
|
new ActionCommitmentDefinition('ACTIONS'),
|
|
20629
20630
|
new ComponentCommitmentDefinition(),
|
|
20630
20631
|
new MetaAvatarCommitmentDefinition(),
|
|
20632
|
+
new MetaAvatarCommitmentDefinition('META VISUAL'),
|
|
20631
20633
|
new MetaImageCommitmentDefinition(),
|
|
20632
20634
|
new MetaColorCommitmentDefinition(),
|
|
20633
20635
|
new MetaFontCommitmentDefinition(),
|
|
@@ -22280,6 +22282,7 @@ function hasHttpProtocol(value) {
|
|
|
22280
22282
|
*/
|
|
22281
22283
|
const META_COMMITMENT_APPLIERS = {
|
|
22282
22284
|
'META AVATAR': applyMetaAvatarContent,
|
|
22285
|
+
'META VISUAL': applyMetaAvatarContent,
|
|
22283
22286
|
'META LINK': applyMetaLinkContent,
|
|
22284
22287
|
'META DOMAIN': applyMetaDomainContent,
|
|
22285
22288
|
'META IMAGE': applyMetaImageContent,
|
|
@@ -22328,7 +22331,7 @@ function applyGenericMetaCommitment(state, content) {
|
|
|
22328
22331
|
state.meta[metaType] = metaValue;
|
|
22329
22332
|
}
|
|
22330
22333
|
/**
|
|
22331
|
-
* Applies META AVATAR content into the canonical `meta.avatar` field.
|
|
22334
|
+
* Applies META AVATAR / META VISUAL content into the canonical `meta.avatar` field.
|
|
22332
22335
|
*/
|
|
22333
22336
|
function applyMetaAvatarContent(state, content) {
|
|
22334
22337
|
const avatarVisualId = resolveAvatarVisualId(content);
|
|
@@ -24500,89 +24503,299 @@ function createExpectationCommandsForUnit(unit, min, max) {
|
|
|
24500
24503
|
if (max !== undefined) {
|
|
24501
24504
|
commands.push(`EXPECT MAX ${max} ${formatExpectationUnit(unit, max)}`);
|
|
24502
24505
|
}
|
|
24503
|
-
return commands;
|
|
24506
|
+
return commands;
|
|
24507
|
+
}
|
|
24508
|
+
/**
|
|
24509
|
+
* Formats the expectation unit exactly as the legacy serializer does.
|
|
24510
|
+
*
|
|
24511
|
+
* @private internal utility of `createTaskSerialization`
|
|
24512
|
+
*/
|
|
24513
|
+
function formatExpectationUnit(unit, amount) {
|
|
24514
|
+
return capitalize(unit + (amount > 1 ? 's' : ''));
|
|
24515
|
+
}
|
|
24516
|
+
/**
|
|
24517
|
+
* Collects format commands.
|
|
24518
|
+
*
|
|
24519
|
+
* @private internal utility of `createTaskSerialization`
|
|
24520
|
+
*/
|
|
24521
|
+
function createFormatCommands(task) {
|
|
24522
|
+
if (task.format === 'JSON') {
|
|
24523
|
+
// TODO: @deprecated remove
|
|
24524
|
+
return ['FORMAT JSON'];
|
|
24525
|
+
}
|
|
24526
|
+
return [];
|
|
24527
|
+
}
|
|
24528
|
+
|
|
24529
|
+
/**
|
|
24530
|
+
* Stringifies one task section of the pipeline.
|
|
24531
|
+
*
|
|
24532
|
+
* @private internal utility of `pipelineJsonToString`
|
|
24533
|
+
*/
|
|
24534
|
+
function stringifyTask(task) {
|
|
24535
|
+
const { title, description, content, resultingParameterName } = task;
|
|
24536
|
+
const { commands, contentLanguage } = createTaskSerialization(task);
|
|
24537
|
+
return spaceTrim$1((block) => `
|
|
24538
|
+
## ${title}
|
|
24539
|
+
|
|
24540
|
+
${block(description || '')}
|
|
24541
|
+
|
|
24542
|
+
${block(stringifyCommands(commands))}
|
|
24543
|
+
|
|
24544
|
+
\`\`\`${contentLanguage}
|
|
24545
|
+
${block(spaceTrim$1(content))}
|
|
24546
|
+
\`\`\`
|
|
24547
|
+
|
|
24548
|
+
\`-> {${resultingParameterName}}\`
|
|
24549
|
+
`); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
|
|
24550
|
+
// <- TODO: [main] !!3 Escape
|
|
24551
|
+
// <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
|
|
24552
|
+
}
|
|
24553
|
+
|
|
24554
|
+
/**
|
|
24555
|
+
* Converts promptbook in JSON format to string format
|
|
24556
|
+
*
|
|
24557
|
+
* @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
|
|
24558
|
+
* @param pipelineJson Promptbook in JSON format (.bookc)
|
|
24559
|
+
* @returns Promptbook in string format (.book.md)
|
|
24560
|
+
*
|
|
24561
|
+
* @public exported from `@promptbook/core`
|
|
24562
|
+
*/
|
|
24563
|
+
function pipelineJsonToString(pipelineJson) {
|
|
24564
|
+
let pipelineString = createPipelineIntroduction(pipelineJson);
|
|
24565
|
+
const pipelineCommands = createPipelineCommands(pipelineJson);
|
|
24566
|
+
pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
|
|
24567
|
+
for (const task of pipelineJson.tasks) {
|
|
24568
|
+
pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
|
|
24569
|
+
}
|
|
24570
|
+
return validatePipelineString(pipelineString);
|
|
24571
|
+
}
|
|
24572
|
+
// TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
|
|
24573
|
+
// TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
|
|
24574
|
+
// TODO: [🏛] Maybe make some markdown builder
|
|
24575
|
+
// TODO: [🏛] Escape all
|
|
24576
|
+
// TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
|
|
24577
|
+
|
|
24578
|
+
/**
|
|
24579
|
+
* This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
|
|
24580
|
+
*
|
|
24581
|
+
* @public exported from `@promptbook/core`
|
|
24582
|
+
*/
|
|
24583
|
+
class PipelineLogicError extends Error {
|
|
24584
|
+
constructor(message) {
|
|
24585
|
+
super(message);
|
|
24586
|
+
this.name = 'PipelineLogicError';
|
|
24587
|
+
Object.setPrototypeOf(this, PipelineLogicError.prototype);
|
|
24588
|
+
}
|
|
24589
|
+
}
|
|
24590
|
+
|
|
24591
|
+
/**
|
|
24592
|
+
* Creates the shared validation context for one pipeline.
|
|
24593
|
+
*
|
|
24594
|
+
* @private function of `validatePipeline`
|
|
24595
|
+
*/
|
|
24596
|
+
function createPipelineValidationContext(pipeline) {
|
|
24597
|
+
return {
|
|
24598
|
+
pipeline,
|
|
24599
|
+
pipelineIdentification: getPipelineIdentification(pipeline),
|
|
24600
|
+
};
|
|
24601
|
+
}
|
|
24602
|
+
/**
|
|
24603
|
+
* Builds a short file/url identification block for validation errors.
|
|
24604
|
+
*
|
|
24605
|
+
* @private internal utility of `validatePipeline`
|
|
24606
|
+
*/
|
|
24607
|
+
function getPipelineIdentification(pipeline) {
|
|
24608
|
+
// Note: This is a 😐 implementation of [🚞]
|
|
24609
|
+
const pipelineIdentificationParts = [];
|
|
24610
|
+
if (pipeline.sourceFile !== undefined) {
|
|
24611
|
+
pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
|
|
24612
|
+
}
|
|
24613
|
+
if (pipeline.pipelineUrl !== undefined) {
|
|
24614
|
+
pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
|
|
24615
|
+
}
|
|
24616
|
+
return pipelineIdentificationParts.join('\n');
|
|
24617
|
+
}
|
|
24618
|
+
|
|
24619
|
+
/**
|
|
24620
|
+
* Validates that the expected top-level collections have array structure.
|
|
24621
|
+
*
|
|
24622
|
+
* @private function of `validatePipeline`
|
|
24623
|
+
*/
|
|
24624
|
+
function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification, }) {
|
|
24625
|
+
validatePipelineParametersCollection(pipeline, pipelineIdentification);
|
|
24626
|
+
validatePipelineTasksCollection(pipeline, pipelineIdentification);
|
|
24627
|
+
}
|
|
24628
|
+
/**
|
|
24629
|
+
* Validates that `pipeline.parameters` is an array.
|
|
24630
|
+
*
|
|
24631
|
+
* @private internal utility of `validatePipelineCollectionsStructure`
|
|
24632
|
+
*/
|
|
24633
|
+
function validatePipelineParametersCollection(pipeline, pipelineIdentification) {
|
|
24634
|
+
// TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
|
|
24635
|
+
if (Array.isArray(pipeline.parameters)) {
|
|
24636
|
+
return;
|
|
24637
|
+
}
|
|
24638
|
+
// TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
|
|
24639
|
+
throw new ParseError(spaceTrim$1((block) => `
|
|
24640
|
+
Pipeline is valid JSON but with wrong structure
|
|
24641
|
+
|
|
24642
|
+
\`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
|
|
24643
|
+
|
|
24644
|
+
${block(pipelineIdentification)}
|
|
24645
|
+
`));
|
|
24646
|
+
}
|
|
24647
|
+
/**
|
|
24648
|
+
* Validates that `pipeline.tasks` is an array.
|
|
24649
|
+
*
|
|
24650
|
+
* @private internal utility of `validatePipelineCollectionsStructure`
|
|
24651
|
+
*/
|
|
24652
|
+
function validatePipelineTasksCollection(pipeline, pipelineIdentification) {
|
|
24653
|
+
// TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
|
|
24654
|
+
if (Array.isArray(pipeline.tasks)) {
|
|
24655
|
+
return;
|
|
24656
|
+
}
|
|
24657
|
+
// TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
|
|
24658
|
+
throw new ParseError(spaceTrim$1((block) => `
|
|
24659
|
+
Pipeline is valid JSON but with wrong structure
|
|
24660
|
+
|
|
24661
|
+
\`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
|
|
24662
|
+
|
|
24663
|
+
${block(pipelineIdentification)}
|
|
24664
|
+
`));
|
|
24665
|
+
}
|
|
24666
|
+
|
|
24667
|
+
/**
|
|
24668
|
+
* Validates that task dependencies can be resolved without cycles or missing definitions.
|
|
24669
|
+
*
|
|
24670
|
+
* @private function of `validatePipeline`
|
|
24671
|
+
*/
|
|
24672
|
+
function validatePipelineDependencyResolution({ pipeline, pipelineIdentification, }) {
|
|
24673
|
+
let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
|
|
24674
|
+
let loopLimit = LOOP_LIMIT;
|
|
24675
|
+
while (hasUnresolvedTasks(dependencyResolutionState)) {
|
|
24676
|
+
if (loopLimit-- < 0) {
|
|
24677
|
+
throw createDependencyResolutionLoopLimitError(pipelineIdentification);
|
|
24678
|
+
}
|
|
24679
|
+
dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
|
|
24680
|
+
}
|
|
24681
|
+
}
|
|
24682
|
+
/**
|
|
24683
|
+
* Collects the parameter names that are already resolvable before dependency traversal starts.
|
|
24684
|
+
*
|
|
24685
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24686
|
+
*/
|
|
24687
|
+
function createInitialDependencyResolutionState(pipeline) {
|
|
24688
|
+
return {
|
|
24689
|
+
resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
|
|
24690
|
+
unresolvedTasks: [...pipeline.tasks],
|
|
24691
|
+
};
|
|
24692
|
+
}
|
|
24693
|
+
/**
|
|
24694
|
+
* Checks whether dependency resolution still has tasks left to process.
|
|
24695
|
+
*
|
|
24696
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24697
|
+
*/
|
|
24698
|
+
function hasUnresolvedTasks({ unresolvedTasks }) {
|
|
24699
|
+
return unresolvedTasks.length > 0;
|
|
24700
|
+
}
|
|
24701
|
+
/**
|
|
24702
|
+
* Resolves the next batch of currently satisfiable tasks.
|
|
24703
|
+
*
|
|
24704
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24705
|
+
*/
|
|
24706
|
+
function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
|
|
24707
|
+
const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
|
|
24708
|
+
if (currentlyResolvedTasks.length === 0) {
|
|
24709
|
+
throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
|
|
24710
|
+
}
|
|
24711
|
+
return {
|
|
24712
|
+
resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
|
|
24713
|
+
unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
|
|
24714
|
+
};
|
|
24715
|
+
}
|
|
24716
|
+
/**
|
|
24717
|
+
* Collects the parameter names that are already resolvable before dependency traversal starts.
|
|
24718
|
+
*
|
|
24719
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24720
|
+
*/
|
|
24721
|
+
function createInitiallyResolvedParameterNames(pipeline) {
|
|
24722
|
+
let resolvedParameterNames = pipeline.parameters
|
|
24723
|
+
.filter(({ isInput }) => isInput)
|
|
24724
|
+
.map(({ name }) => name);
|
|
24725
|
+
for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
|
|
24726
|
+
resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
|
|
24727
|
+
}
|
|
24728
|
+
return resolvedParameterNames;
|
|
24504
24729
|
}
|
|
24505
24730
|
/**
|
|
24506
|
-
*
|
|
24731
|
+
* Adds newly resolved task outputs to the resolved parameter list.
|
|
24507
24732
|
*
|
|
24508
|
-
* @private internal utility of `
|
|
24733
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24509
24734
|
*/
|
|
24510
|
-
function
|
|
24511
|
-
return
|
|
24735
|
+
function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
|
|
24736
|
+
return [
|
|
24737
|
+
...resolvedParameterNames,
|
|
24738
|
+
...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
|
|
24739
|
+
];
|
|
24512
24740
|
}
|
|
24513
24741
|
/**
|
|
24514
|
-
*
|
|
24742
|
+
* Selects tasks whose dependencies are already resolved.
|
|
24515
24743
|
*
|
|
24516
|
-
* @private internal utility of `
|
|
24744
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24517
24745
|
*/
|
|
24518
|
-
function
|
|
24519
|
-
|
|
24520
|
-
// TODO: @deprecated remove
|
|
24521
|
-
return ['FORMAT JSON'];
|
|
24522
|
-
}
|
|
24523
|
-
return [];
|
|
24746
|
+
function getCurrentlyResolvedTasks(unresolvedTasks, resolvedParameterNames) {
|
|
24747
|
+
return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
|
|
24524
24748
|
}
|
|
24749
|
+
/**
|
|
24750
|
+
* Creates the unexpected loop-limit error for dependency resolution.
|
|
24751
|
+
*
|
|
24752
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24753
|
+
*/
|
|
24754
|
+
function createDependencyResolutionLoopLimitError(pipelineIdentification) {
|
|
24755
|
+
// Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
|
|
24756
|
+
return new UnexpectedError(spaceTrim$1((block) => `
|
|
24757
|
+
Loop limit reached during detection of circular dependencies in \`validatePipeline\`
|
|
24525
24758
|
|
|
24759
|
+
${block(pipelineIdentification)}
|
|
24760
|
+
`));
|
|
24761
|
+
}
|
|
24526
24762
|
/**
|
|
24527
|
-
*
|
|
24763
|
+
* Creates the detailed error for unresolved or circular task dependencies.
|
|
24528
24764
|
*
|
|
24529
|
-
* @private internal utility of `
|
|
24765
|
+
* @private internal utility of `validatePipelineDependencyResolution`
|
|
24530
24766
|
*/
|
|
24531
|
-
function
|
|
24532
|
-
|
|
24533
|
-
|
|
24534
|
-
|
|
24535
|
-
## ${title}
|
|
24767
|
+
function createUnresolvedTasksError(unresolvedTasks, resolvedParameterNames, pipelineIdentification) {
|
|
24768
|
+
return new PipelineLogicError(
|
|
24769
|
+
// TODO: [🐎] DRY
|
|
24770
|
+
spaceTrim$1((block) => `
|
|
24536
24771
|
|
|
24537
|
-
|
|
24772
|
+
Can not resolve some parameters:
|
|
24773
|
+
Either you are using a parameter that is not defined, or there are some circular dependencies.
|
|
24538
24774
|
|
|
24539
|
-
|
|
24775
|
+
${block(pipelineIdentification)}
|
|
24540
24776
|
|
|
24541
|
-
|
|
24542
|
-
|
|
24543
|
-
|
|
24777
|
+
**Can not resolve:**
|
|
24778
|
+
${block(unresolvedTasks
|
|
24779
|
+
.map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
|
|
24780
|
+
.map((dependentParameterName) => `\`{${dependentParameterName}}\``)
|
|
24781
|
+
.join(' and ')}`)
|
|
24782
|
+
.join('\n'))}
|
|
24544
24783
|
|
|
24545
|
-
|
|
24546
|
-
|
|
24547
|
-
|
|
24548
|
-
|
|
24549
|
-
}
|
|
24784
|
+
**Resolved:**
|
|
24785
|
+
${block(resolvedParameterNames
|
|
24786
|
+
.filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
|
|
24787
|
+
.map((name) => `- Parameter \`{${name}}\``)
|
|
24788
|
+
.join('\n'))}
|
|
24550
24789
|
|
|
24551
|
-
/**
|
|
24552
|
-
* Converts promptbook in JSON format to string format
|
|
24553
|
-
*
|
|
24554
|
-
* @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
|
|
24555
|
-
* @param pipelineJson Promptbook in JSON format (.bookc)
|
|
24556
|
-
* @returns Promptbook in string format (.book.md)
|
|
24557
|
-
*
|
|
24558
|
-
* @public exported from `@promptbook/core`
|
|
24559
|
-
*/
|
|
24560
|
-
function pipelineJsonToString(pipelineJson) {
|
|
24561
|
-
let pipelineString = createPipelineIntroduction(pipelineJson);
|
|
24562
|
-
const pipelineCommands = createPipelineCommands(pipelineJson);
|
|
24563
|
-
pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
|
|
24564
|
-
for (const task of pipelineJson.tasks) {
|
|
24565
|
-
pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
|
|
24566
|
-
}
|
|
24567
|
-
return validatePipelineString(pipelineString);
|
|
24568
|
-
}
|
|
24569
|
-
// TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
|
|
24570
|
-
// TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
|
|
24571
|
-
// TODO: [🏛] Maybe make some markdown builder
|
|
24572
|
-
// TODO: [🏛] Escape all
|
|
24573
|
-
// TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
|
|
24574
24790
|
|
|
24575
|
-
|
|
24576
|
-
|
|
24577
|
-
|
|
24578
|
-
|
|
24579
|
-
|
|
24580
|
-
|
|
24581
|
-
|
|
24582
|
-
|
|
24583
|
-
this.name = 'PipelineLogicError';
|
|
24584
|
-
Object.setPrototypeOf(this, PipelineLogicError.prototype);
|
|
24585
|
-
}
|
|
24791
|
+
**Reserved (which are available):**
|
|
24792
|
+
${block(resolvedParameterNames
|
|
24793
|
+
.filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
|
|
24794
|
+
.map((name) => `- Parameter \`{${name}}\``)
|
|
24795
|
+
.join('\n'))}
|
|
24796
|
+
|
|
24797
|
+
|
|
24798
|
+
`));
|
|
24586
24799
|
}
|
|
24587
24800
|
|
|
24588
24801
|
/**
|
|
@@ -24645,182 +24858,33 @@ function isValidPipelineUrl(url) {
|
|
|
24645
24858
|
if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
|
|
24646
24859
|
return false;
|
|
24647
24860
|
}
|
|
24648
|
-
if (url.includes('#')) {
|
|
24649
|
-
// TODO: [🐠]
|
|
24650
|
-
return false;
|
|
24651
|
-
}
|
|
24652
|
-
/*
|
|
24653
|
-
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
24654
|
-
if (isUrlOnPrivateNetwork(url)) {
|
|
24655
|
-
return false;
|
|
24656
|
-
}
|
|
24657
|
-
*/
|
|
24658
|
-
return true;
|
|
24659
|
-
}
|
|
24660
|
-
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
24661
|
-
|
|
24662
|
-
/**
|
|
24663
|
-
* Validates PipelineJson if it is logically valid
|
|
24664
|
-
*
|
|
24665
|
-
* It checks:
|
|
24666
|
-
* - if it has correct parameters dependency
|
|
24667
|
-
*
|
|
24668
|
-
* It does NOT check:
|
|
24669
|
-
* - if it is valid json
|
|
24670
|
-
* - if it is meaningful
|
|
24671
|
-
*
|
|
24672
|
-
* Note: [🔂] This function is idempotent.
|
|
24673
|
-
*
|
|
24674
|
-
* @param pipeline valid or invalid PipelineJson
|
|
24675
|
-
* @returns the same pipeline if it is logically valid
|
|
24676
|
-
* @throws {PipelineLogicError} on logical error in the pipeline
|
|
24677
|
-
*
|
|
24678
|
-
* @public exported from `@promptbook/core`
|
|
24679
|
-
*/
|
|
24680
|
-
function validatePipeline(pipeline) {
|
|
24681
|
-
if (IS_PIPELINE_LOGIC_VALIDATED) {
|
|
24682
|
-
validatePipeline_InnerFunction(pipeline);
|
|
24683
|
-
}
|
|
24684
|
-
else {
|
|
24685
|
-
try {
|
|
24686
|
-
validatePipeline_InnerFunction(pipeline);
|
|
24687
|
-
}
|
|
24688
|
-
catch (error) {
|
|
24689
|
-
if (!(error instanceof PipelineLogicError)) {
|
|
24690
|
-
throw error;
|
|
24691
|
-
}
|
|
24692
|
-
console.error(spaceTrim$1((block) => `
|
|
24693
|
-
Pipeline is not valid but logic errors are temporarily disabled via \`IS_PIPELINE_LOGIC_VALIDATED\`
|
|
24694
|
-
|
|
24695
|
-
${block(error.message)}
|
|
24696
|
-
`));
|
|
24697
|
-
}
|
|
24698
|
-
}
|
|
24699
|
-
return pipeline;
|
|
24700
|
-
}
|
|
24701
|
-
/**
|
|
24702
|
-
* Validates pipeline inner function.
|
|
24703
|
-
*
|
|
24704
|
-
* @private internal function for `validatePipeline`
|
|
24705
|
-
*/
|
|
24706
|
-
function validatePipeline_InnerFunction(pipeline) {
|
|
24707
|
-
// TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
|
|
24708
|
-
const context = createPipelineValidationContext(pipeline);
|
|
24709
|
-
validatePipelineMetadata(context);
|
|
24710
|
-
validatePipelineCollectionsStructure(context);
|
|
24711
|
-
validatePipelineParameters(context);
|
|
24712
|
-
validatePipelineTasks(context);
|
|
24713
|
-
validatePipelineDependencyResolution(context);
|
|
24714
|
-
// Note: Check that formfactor is corresponding to the pipeline interface
|
|
24715
|
-
// TODO: !!6 Implement this
|
|
24716
|
-
// pipeline.formfactorName
|
|
24717
|
-
}
|
|
24718
|
-
/**
|
|
24719
|
-
* Creates the shared validation context for one pipeline.
|
|
24720
|
-
*
|
|
24721
|
-
* @private internal utility of `validatePipeline`
|
|
24722
|
-
*/
|
|
24723
|
-
function createPipelineValidationContext(pipeline) {
|
|
24724
|
-
return {
|
|
24725
|
-
pipeline,
|
|
24726
|
-
pipelineIdentification: getPipelineIdentification(pipeline),
|
|
24727
|
-
};
|
|
24728
|
-
}
|
|
24729
|
-
/**
|
|
24730
|
-
* Builds a short file/url identification block for validation errors.
|
|
24731
|
-
*
|
|
24732
|
-
* @private internal utility of `validatePipeline`
|
|
24733
|
-
*/
|
|
24734
|
-
function getPipelineIdentification(pipeline) {
|
|
24735
|
-
// Note: This is a 😐 implementation of [🚞]
|
|
24736
|
-
const pipelineIdentificationParts = [];
|
|
24737
|
-
if (pipeline.sourceFile !== undefined) {
|
|
24738
|
-
pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
|
|
24739
|
-
}
|
|
24740
|
-
if (pipeline.pipelineUrl !== undefined) {
|
|
24741
|
-
pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
|
|
24742
|
-
}
|
|
24743
|
-
return pipelineIdentificationParts.join('\n');
|
|
24744
|
-
}
|
|
24745
|
-
/**
|
|
24746
|
-
* Validates pipeline-level metadata fields.
|
|
24747
|
-
*
|
|
24748
|
-
* @private internal step of `validatePipeline`
|
|
24749
|
-
*/
|
|
24750
|
-
function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
|
|
24751
|
-
validatePipelineUrl(pipeline, pipelineIdentification);
|
|
24752
|
-
validatePipelineBookVersion(pipeline, pipelineIdentification);
|
|
24753
|
-
}
|
|
24754
|
-
/**
|
|
24755
|
-
* Validates that the expected top-level collections have array structure.
|
|
24756
|
-
*
|
|
24757
|
-
* @private internal step of `validatePipeline`
|
|
24758
|
-
*/
|
|
24759
|
-
function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification }) {
|
|
24760
|
-
validatePipelineParametersCollection(pipeline, pipelineIdentification);
|
|
24761
|
-
validatePipelineTasksCollection(pipeline, pipelineIdentification);
|
|
24762
|
-
}
|
|
24763
|
-
/**
|
|
24764
|
-
* Validates all pipeline parameter declarations.
|
|
24765
|
-
*
|
|
24766
|
-
* @private internal step of `validatePipeline`
|
|
24767
|
-
*/
|
|
24768
|
-
function validatePipelineParameters({ pipeline, pipelineIdentification }) {
|
|
24769
|
-
for (const parameter of pipeline.parameters) {
|
|
24770
|
-
validatePipelineParameter(parameter, pipeline, pipelineIdentification);
|
|
24771
|
-
}
|
|
24772
|
-
}
|
|
24773
|
-
/**
|
|
24774
|
-
* Validates all pipeline tasks and their per-task invariants.
|
|
24775
|
-
*
|
|
24776
|
-
* @private internal step of `validatePipeline`
|
|
24777
|
-
*/
|
|
24778
|
-
function validatePipelineTasks({ pipeline, pipelineIdentification }) {
|
|
24779
|
-
// Note: All input parameters are defined - so that they can be used as result of some task
|
|
24780
|
-
const definedParameters = createInitiallyDefinedParameters(pipeline);
|
|
24781
|
-
for (const task of pipeline.tasks) {
|
|
24782
|
-
validatePipelineTask(task, definedParameters, pipelineIdentification);
|
|
24783
|
-
}
|
|
24784
|
-
}
|
|
24785
|
-
/**
|
|
24786
|
-
* Validates that task dependencies can be resolved without cycles or missing definitions.
|
|
24787
|
-
*
|
|
24788
|
-
* @private internal step of `validatePipeline`
|
|
24789
|
-
*/
|
|
24790
|
-
function validatePipelineDependencyResolution({ pipeline, pipelineIdentification }) {
|
|
24791
|
-
let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
|
|
24792
|
-
let loopLimit = LOOP_LIMIT;
|
|
24793
|
-
while (hasUnresolvedTasks(dependencyResolutionState)) {
|
|
24794
|
-
if (loopLimit-- < 0) {
|
|
24795
|
-
throw createDependencyResolutionLoopLimitError(pipelineIdentification);
|
|
24796
|
-
}
|
|
24797
|
-
dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
|
|
24861
|
+
if (url.includes('#')) {
|
|
24862
|
+
// TODO: [🐠]
|
|
24863
|
+
return false;
|
|
24798
24864
|
}
|
|
24865
|
+
/*
|
|
24866
|
+
Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
|
|
24867
|
+
if (isUrlOnPrivateNetwork(url)) {
|
|
24868
|
+
return false;
|
|
24869
|
+
}
|
|
24870
|
+
*/
|
|
24871
|
+
return true;
|
|
24799
24872
|
}
|
|
24873
|
+
// TODO: [🐠] Maybe more info why the URL is invalid
|
|
24874
|
+
|
|
24800
24875
|
/**
|
|
24801
|
-
* Validates
|
|
24802
|
-
*
|
|
24803
|
-
* @private internal step of `validatePipeline`
|
|
24804
|
-
*/
|
|
24805
|
-
function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
|
|
24806
|
-
validateParameterDirection(parameter, pipelineIdentification);
|
|
24807
|
-
validateParameterUsage(parameter, pipeline, pipelineIdentification);
|
|
24808
|
-
validateParameterDefinition(parameter, pipeline, pipelineIdentification);
|
|
24809
|
-
}
|
|
24810
|
-
/**
|
|
24811
|
-
* Validates one pipeline task and its invariants.
|
|
24876
|
+
* Validates pipeline-level metadata fields.
|
|
24812
24877
|
*
|
|
24813
|
-
* @private
|
|
24878
|
+
* @private function of `validatePipeline`
|
|
24814
24879
|
*/
|
|
24815
|
-
function
|
|
24816
|
-
|
|
24817
|
-
|
|
24818
|
-
validateTaskExpectations(task, pipelineIdentification);
|
|
24880
|
+
function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
|
|
24881
|
+
validatePipelineUrl(pipeline, pipelineIdentification);
|
|
24882
|
+
validatePipelineBookVersion(pipeline, pipelineIdentification);
|
|
24819
24883
|
}
|
|
24820
24884
|
/**
|
|
24821
24885
|
* Validates the pipeline URL, when present.
|
|
24822
24886
|
*
|
|
24823
|
-
* @private internal utility of `
|
|
24887
|
+
* @private internal utility of `validatePipelineMetadata`
|
|
24824
24888
|
*/
|
|
24825
24889
|
function validatePipelineUrl(pipeline, pipelineIdentification) {
|
|
24826
24890
|
if (pipeline.pipelineUrl === undefined || isValidPipelineUrl(pipeline.pipelineUrl)) {
|
|
@@ -24836,7 +24900,7 @@ function validatePipelineUrl(pipeline, pipelineIdentification) {
|
|
|
24836
24900
|
/**
|
|
24837
24901
|
* Validates the Promptbook version, when present.
|
|
24838
24902
|
*
|
|
24839
|
-
* @private internal utility of `
|
|
24903
|
+
* @private internal utility of `validatePipelineMetadata`
|
|
24840
24904
|
*/
|
|
24841
24905
|
function validatePipelineBookVersion(pipeline, pipelineIdentification) {
|
|
24842
24906
|
if (pipeline.bookVersion === undefined || isValidPromptbookVersion(pipeline.bookVersion)) {
|
|
@@ -24849,48 +24913,31 @@ function validatePipelineBookVersion(pipeline, pipelineIdentification) {
|
|
|
24849
24913
|
${block(pipelineIdentification)}
|
|
24850
24914
|
`));
|
|
24851
24915
|
}
|
|
24916
|
+
|
|
24852
24917
|
/**
|
|
24853
|
-
* Validates
|
|
24918
|
+
* Validates all pipeline parameter declarations.
|
|
24854
24919
|
*
|
|
24855
|
-
* @private
|
|
24920
|
+
* @private function of `validatePipeline`
|
|
24856
24921
|
*/
|
|
24857
|
-
function
|
|
24858
|
-
|
|
24859
|
-
|
|
24860
|
-
return;
|
|
24922
|
+
function validatePipelineParameters({ pipeline, pipelineIdentification }) {
|
|
24923
|
+
for (const parameter of pipeline.parameters) {
|
|
24924
|
+
validatePipelineParameter(parameter, pipeline, pipelineIdentification);
|
|
24861
24925
|
}
|
|
24862
|
-
// TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
|
|
24863
|
-
throw new ParseError(spaceTrim$1((block) => `
|
|
24864
|
-
Pipeline is valid JSON but with wrong structure
|
|
24865
|
-
|
|
24866
|
-
\`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
|
|
24867
|
-
|
|
24868
|
-
${block(pipelineIdentification)}
|
|
24869
|
-
`));
|
|
24870
24926
|
}
|
|
24871
24927
|
/**
|
|
24872
|
-
* Validates
|
|
24928
|
+
* Validates one pipeline parameter declaration.
|
|
24873
24929
|
*
|
|
24874
|
-
* @private internal utility of `
|
|
24930
|
+
* @private internal utility of `validatePipelineParameters`
|
|
24875
24931
|
*/
|
|
24876
|
-
function
|
|
24877
|
-
|
|
24878
|
-
|
|
24879
|
-
|
|
24880
|
-
}
|
|
24881
|
-
// TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
|
|
24882
|
-
throw new ParseError(spaceTrim$1((block) => `
|
|
24883
|
-
Pipeline is valid JSON but with wrong structure
|
|
24884
|
-
|
|
24885
|
-
\`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
|
|
24886
|
-
|
|
24887
|
-
${block(pipelineIdentification)}
|
|
24888
|
-
`));
|
|
24932
|
+
function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
|
|
24933
|
+
validateParameterDirection(parameter, pipelineIdentification);
|
|
24934
|
+
validateParameterUsage(parameter, pipeline, pipelineIdentification);
|
|
24935
|
+
validateParameterDefinition(parameter, pipeline, pipelineIdentification);
|
|
24889
24936
|
}
|
|
24890
24937
|
/**
|
|
24891
24938
|
* Validates that one parameter does not declare incompatible directions.
|
|
24892
24939
|
*
|
|
24893
|
-
* @private internal utility of `
|
|
24940
|
+
* @private internal utility of `validatePipelineParameters`
|
|
24894
24941
|
*/
|
|
24895
24942
|
function validateParameterDirection(parameter, pipelineIdentification) {
|
|
24896
24943
|
if (!parameter.isInput || !parameter.isOutput) {
|
|
@@ -24907,7 +24954,7 @@ function validateParameterDirection(parameter, pipelineIdentification) {
|
|
|
24907
24954
|
/**
|
|
24908
24955
|
* Validates that one intermediate parameter is actually consumed by at least one task.
|
|
24909
24956
|
*
|
|
24910
|
-
* @private internal utility of `
|
|
24957
|
+
* @private internal utility of `validatePipelineParameters`
|
|
24911
24958
|
*/
|
|
24912
24959
|
function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
|
|
24913
24960
|
if (parameter.isInput || parameter.isOutput || isParameterUsedByAnyTask(parameter, pipeline.tasks)) {
|
|
@@ -24926,7 +24973,7 @@ function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
|
|
|
24926
24973
|
/**
|
|
24927
24974
|
* Validates that one non-input parameter is produced by at least one task.
|
|
24928
24975
|
*
|
|
24929
|
-
* @private internal utility of `
|
|
24976
|
+
* @private internal utility of `validatePipelineParameters`
|
|
24930
24977
|
*/
|
|
24931
24978
|
function validateParameterDefinition(parameter, pipeline, pipelineIdentification) {
|
|
24932
24979
|
if (parameter.isInput || isParameterDefinedByAnyTask(parameter, pipeline.tasks)) {
|
|
@@ -24945,7 +24992,7 @@ function validateParameterDefinition(parameter, pipeline, pipelineIdentification
|
|
|
24945
24992
|
/**
|
|
24946
24993
|
* Checks whether one parameter is consumed by at least one task dependency list.
|
|
24947
24994
|
*
|
|
24948
|
-
* @private internal utility of `
|
|
24995
|
+
* @private internal utility of `validatePipelineParameters`
|
|
24949
24996
|
*/
|
|
24950
24997
|
function isParameterUsedByAnyTask(parameter, tasks) {
|
|
24951
24998
|
return tasks.some((task) => task.dependentParameterNames.includes(parameter.name));
|
|
@@ -24953,23 +25000,46 @@ function isParameterUsedByAnyTask(parameter, tasks) {
|
|
|
24953
25000
|
/**
|
|
24954
25001
|
* Checks whether one parameter is produced by at least one task.
|
|
24955
25002
|
*
|
|
24956
|
-
* @private internal utility of `
|
|
25003
|
+
* @private internal utility of `validatePipelineParameters`
|
|
24957
25004
|
*/
|
|
24958
25005
|
function isParameterDefinedByAnyTask(parameter, tasks) {
|
|
24959
25006
|
return tasks.some((task) => task.resultingParameterName === parameter.name);
|
|
24960
25007
|
}
|
|
25008
|
+
|
|
25009
|
+
/**
|
|
25010
|
+
* Validates all pipeline tasks and their per-task invariants.
|
|
25011
|
+
*
|
|
25012
|
+
* @private function of `validatePipeline`
|
|
25013
|
+
*/
|
|
25014
|
+
function validatePipelineTasks({ pipeline, pipelineIdentification }) {
|
|
25015
|
+
// Note: All input parameters are defined - so that they can be used as result of some task
|
|
25016
|
+
const definedParameters = createInitiallyDefinedParameters(pipeline);
|
|
25017
|
+
for (const task of pipeline.tasks) {
|
|
25018
|
+
validatePipelineTask(task, definedParameters, pipelineIdentification);
|
|
25019
|
+
}
|
|
25020
|
+
}
|
|
24961
25021
|
/**
|
|
24962
25022
|
* Collects the parameter names that are already defined before task validation starts.
|
|
24963
25023
|
*
|
|
24964
|
-
* @private internal utility of `
|
|
25024
|
+
* @private internal utility of `validatePipelineTasks`
|
|
24965
25025
|
*/
|
|
24966
25026
|
function createInitiallyDefinedParameters(pipeline) {
|
|
24967
25027
|
return new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
|
|
24968
25028
|
}
|
|
25029
|
+
/**
|
|
25030
|
+
* Validates one pipeline task and its invariants.
|
|
25031
|
+
*
|
|
25032
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25033
|
+
*/
|
|
25034
|
+
function validatePipelineTask(task, definedParameters, pipelineIdentification) {
|
|
25035
|
+
validateTaskResultingParameter(task, definedParameters, pipelineIdentification);
|
|
25036
|
+
validateTaskJokers(task, pipelineIdentification);
|
|
25037
|
+
validateTaskExpectations(task, pipelineIdentification);
|
|
25038
|
+
}
|
|
24969
25039
|
/**
|
|
24970
25040
|
* Validates one task result parameter declaration and marks it as defined.
|
|
24971
25041
|
*
|
|
24972
|
-
* @private internal utility of `
|
|
25042
|
+
* @private internal utility of `validatePipelineTasks`
|
|
24973
25043
|
*/
|
|
24974
25044
|
function validateTaskResultingParameter(task, definedParameters, pipelineIdentification) {
|
|
24975
25045
|
if (definedParameters.has(task.resultingParameterName)) {
|
|
@@ -24991,7 +25061,7 @@ function validateTaskResultingParameter(task, definedParameters, pipelineIdentif
|
|
|
24991
25061
|
/**
|
|
24992
25062
|
* Validates joker parameters for one task.
|
|
24993
25063
|
*
|
|
24994
|
-
* @private internal utility of `
|
|
25064
|
+
* @private internal utility of `validatePipelineTasks`
|
|
24995
25065
|
*/
|
|
24996
25066
|
function validateTaskJokers(task, pipelineIdentification) {
|
|
24997
25067
|
if (!hasTaskJokers(task)) {
|
|
@@ -25003,7 +25073,7 @@ function validateTaskJokers(task, pipelineIdentification) {
|
|
|
25003
25073
|
/**
|
|
25004
25074
|
* Checks whether one task declares any joker parameters.
|
|
25005
25075
|
*
|
|
25006
|
-
* @private internal utility of `
|
|
25076
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25007
25077
|
*/
|
|
25008
25078
|
function hasTaskJokers(task) {
|
|
25009
25079
|
return !!task.jokerParameterNames && task.jokerParameterNames.length > 0;
|
|
@@ -25011,7 +25081,7 @@ function hasTaskJokers(task) {
|
|
|
25011
25081
|
/**
|
|
25012
25082
|
* Validates that a task has the required supporting features when using jokers.
|
|
25013
25083
|
*
|
|
25014
|
-
* @private internal utility of `
|
|
25084
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25015
25085
|
*/
|
|
25016
25086
|
function validateTaskSupportsJokers(task, pipelineIdentification) {
|
|
25017
25087
|
if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
|
|
@@ -25026,7 +25096,7 @@ function validateTaskSupportsJokers(task, pipelineIdentification) {
|
|
|
25026
25096
|
/**
|
|
25027
25097
|
* Validates that every joker parameter is also listed among task dependencies.
|
|
25028
25098
|
*
|
|
25029
|
-
* @private internal utility of `
|
|
25099
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25030
25100
|
*/
|
|
25031
25101
|
function validateTaskJokerDependencies(task, pipelineIdentification) {
|
|
25032
25102
|
for (const joker of task.jokerParameterNames) {
|
|
@@ -25043,7 +25113,7 @@ function validateTaskJokerDependencies(task, pipelineIdentification) {
|
|
|
25043
25113
|
/**
|
|
25044
25114
|
* Validates all expectation bounds configured on one task.
|
|
25045
25115
|
*
|
|
25046
|
-
* @private internal utility of `
|
|
25116
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25047
25117
|
*/
|
|
25048
25118
|
function validateTaskExpectations(task, pipelineIdentification) {
|
|
25049
25119
|
if (!task.expectations) {
|
|
@@ -25058,7 +25128,7 @@ function validateTaskExpectations(task, pipelineIdentification) {
|
|
|
25058
25128
|
/**
|
|
25059
25129
|
* Validates the minimum and maximum expectation ordering for one unit.
|
|
25060
25130
|
*
|
|
25061
|
-
* @private internal utility of `
|
|
25131
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25062
25132
|
*/
|
|
25063
25133
|
function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
|
|
25064
25134
|
if (min === undefined || max === undefined || min <= max) {
|
|
@@ -25073,7 +25143,7 @@ function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
|
|
|
25073
25143
|
/**
|
|
25074
25144
|
* Validates the minimum expectation bound for one unit.
|
|
25075
25145
|
*
|
|
25076
|
-
* @private internal utility of `
|
|
25146
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25077
25147
|
*/
|
|
25078
25148
|
function validateTaskExpectationMin(unit, min, pipelineIdentification) {
|
|
25079
25149
|
if (min === undefined || min >= 0) {
|
|
@@ -25088,7 +25158,7 @@ function validateTaskExpectationMin(unit, min, pipelineIdentification) {
|
|
|
25088
25158
|
/**
|
|
25089
25159
|
* Validates the maximum expectation bound for one unit.
|
|
25090
25160
|
*
|
|
25091
|
-
* @private internal utility of `
|
|
25161
|
+
* @private internal utility of `validatePipelineTasks`
|
|
25092
25162
|
*/
|
|
25093
25163
|
function validateTaskExpectationMax(unit, max, pipelineIdentification) {
|
|
25094
25164
|
if (max === undefined || max > 0) {
|
|
@@ -25100,123 +25170,62 @@ function validateTaskExpectationMax(unit, max, pipelineIdentification) {
|
|
|
25100
25170
|
${block(pipelineIdentification)}
|
|
25101
25171
|
`));
|
|
25102
25172
|
}
|
|
25173
|
+
|
|
25103
25174
|
/**
|
|
25104
|
-
*
|
|
25105
|
-
*
|
|
25106
|
-
* @private internal utility of `validatePipeline`
|
|
25107
|
-
*/
|
|
25108
|
-
function createInitialDependencyResolutionState(pipeline) {
|
|
25109
|
-
return {
|
|
25110
|
-
resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
|
|
25111
|
-
unresolvedTasks: [...pipeline.tasks],
|
|
25112
|
-
};
|
|
25113
|
-
}
|
|
25114
|
-
/**
|
|
25115
|
-
* Checks whether dependency resolution still has tasks left to process.
|
|
25116
|
-
*
|
|
25117
|
-
* @private internal utility of `validatePipeline`
|
|
25118
|
-
*/
|
|
25119
|
-
function hasUnresolvedTasks({ unresolvedTasks }) {
|
|
25120
|
-
return unresolvedTasks.length > 0;
|
|
25121
|
-
}
|
|
25122
|
-
/**
|
|
25123
|
-
* Resolves the next batch of currently satisfiable tasks.
|
|
25175
|
+
* Validates PipelineJson if it is logically valid
|
|
25124
25176
|
*
|
|
25125
|
-
*
|
|
25126
|
-
|
|
25127
|
-
function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
|
|
25128
|
-
const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
|
|
25129
|
-
if (currentlyResolvedTasks.length === 0) {
|
|
25130
|
-
throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
|
|
25131
|
-
}
|
|
25132
|
-
return {
|
|
25133
|
-
resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
|
|
25134
|
-
unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
|
|
25135
|
-
};
|
|
25136
|
-
}
|
|
25137
|
-
/**
|
|
25138
|
-
* Collects the parameter names that are already resolvable before dependency traversal starts.
|
|
25177
|
+
* It checks:
|
|
25178
|
+
* - if it has correct parameters dependency
|
|
25139
25179
|
*
|
|
25140
|
-
*
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
let resolvedParameterNames = pipeline.parameters
|
|
25144
|
-
.filter(({ isInput }) => isInput)
|
|
25145
|
-
.map(({ name }) => name);
|
|
25146
|
-
for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
|
|
25147
|
-
resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
|
|
25148
|
-
}
|
|
25149
|
-
return resolvedParameterNames;
|
|
25150
|
-
}
|
|
25151
|
-
/**
|
|
25152
|
-
* Adds newly resolved task outputs to the resolved parameter list.
|
|
25180
|
+
* It does NOT check:
|
|
25181
|
+
* - if it is valid json
|
|
25182
|
+
* - if it is meaningful
|
|
25153
25183
|
*
|
|
25154
|
-
*
|
|
25155
|
-
*/
|
|
25156
|
-
function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
|
|
25157
|
-
return [
|
|
25158
|
-
...resolvedParameterNames,
|
|
25159
|
-
...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
|
|
25160
|
-
];
|
|
25161
|
-
}
|
|
25162
|
-
/**
|
|
25163
|
-
* Selects tasks whose dependencies are already resolved.
|
|
25184
|
+
* Note: [🔂] This function is idempotent.
|
|
25164
25185
|
*
|
|
25165
|
-
* @
|
|
25166
|
-
|
|
25167
|
-
|
|
25168
|
-
return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
|
|
25169
|
-
}
|
|
25170
|
-
/**
|
|
25171
|
-
* Creates the unexpected loop-limit error for dependency resolution.
|
|
25186
|
+
* @param pipeline valid or invalid PipelineJson
|
|
25187
|
+
* @returns the same pipeline if it is logically valid
|
|
25188
|
+
* @throws {PipelineLogicError} on logical error in the pipeline
|
|
25172
25189
|
*
|
|
25173
|
-
* @
|
|
25190
|
+
* @public exported from `@promptbook/core`
|
|
25174
25191
|
*/
|
|
25175
|
-
function
|
|
25176
|
-
|
|
25177
|
-
|
|
25178
|
-
|
|
25192
|
+
function validatePipeline(pipeline) {
|
|
25193
|
+
if (IS_PIPELINE_LOGIC_VALIDATED) {
|
|
25194
|
+
validatePipeline_InnerFunction(pipeline);
|
|
25195
|
+
}
|
|
25196
|
+
else {
|
|
25197
|
+
try {
|
|
25198
|
+
validatePipeline_InnerFunction(pipeline);
|
|
25199
|
+
}
|
|
25200
|
+
catch (error) {
|
|
25201
|
+
if (!(error instanceof PipelineLogicError)) {
|
|
25202
|
+
throw error;
|
|
25203
|
+
}
|
|
25204
|
+
console.error(spaceTrim$1((block) => `
|
|
25205
|
+
Pipeline is not valid but logic errors are temporarily disabled via \`IS_PIPELINE_LOGIC_VALIDATED\`
|
|
25179
25206
|
|
|
25180
|
-
|
|
25181
|
-
|
|
25207
|
+
${block(error.message)}
|
|
25208
|
+
`));
|
|
25209
|
+
}
|
|
25210
|
+
}
|
|
25211
|
+
return pipeline;
|
|
25182
25212
|
}
|
|
25183
25213
|
/**
|
|
25184
|
-
*
|
|
25214
|
+
* Validates pipeline inner function.
|
|
25185
25215
|
*
|
|
25186
|
-
* @private internal
|
|
25216
|
+
* @private internal function for `validatePipeline`
|
|
25187
25217
|
*/
|
|
25188
|
-
function
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25195
|
-
|
|
25196
|
-
|
|
25197
|
-
|
|
25198
|
-
|
|
25199
|
-
${block(unresolvedTasks
|
|
25200
|
-
.map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
|
|
25201
|
-
.map((dependentParameterName) => `\`{${dependentParameterName}}\``)
|
|
25202
|
-
.join(' and ')}`)
|
|
25203
|
-
.join('\n'))}
|
|
25204
|
-
|
|
25205
|
-
**Resolved:**
|
|
25206
|
-
${block(resolvedParameterNames
|
|
25207
|
-
.filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
|
|
25208
|
-
.map((name) => `- Parameter \`{${name}}\``)
|
|
25209
|
-
.join('\n'))}
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
**Reserved (which are available):**
|
|
25213
|
-
${block(resolvedParameterNames
|
|
25214
|
-
.filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
|
|
25215
|
-
.map((name) => `- Parameter \`{${name}}\``)
|
|
25216
|
-
.join('\n'))}
|
|
25217
|
-
|
|
25218
|
-
|
|
25219
|
-
`));
|
|
25218
|
+
function validatePipeline_InnerFunction(pipeline) {
|
|
25219
|
+
// TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
|
|
25220
|
+
const context = createPipelineValidationContext(pipeline);
|
|
25221
|
+
validatePipelineMetadata(context);
|
|
25222
|
+
validatePipelineCollectionsStructure(context);
|
|
25223
|
+
validatePipelineParameters(context);
|
|
25224
|
+
validatePipelineTasks(context);
|
|
25225
|
+
validatePipelineDependencyResolution(context);
|
|
25226
|
+
// Note: Check that formfactor is corresponding to the pipeline interface
|
|
25227
|
+
// TODO: !!6 Implement this
|
|
25228
|
+
// pipeline.formfactorName
|
|
25220
25229
|
}
|
|
25221
25230
|
/**
|
|
25222
25231
|
* TODO: [🧞♀️] Do not allow joker + foreach
|