@heyramzi/ui 0.0.0-stage → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +141 -0
- package/README.md +130 -2
- package/package.json +215 -3
- package/scripts/lib/sync-core.mjs +113 -0
- package/scripts/relative-imports.mjs +39 -0
- package/scripts/sf-symbol.mjs +93 -0
- package/scripts/sync-shadcn.mjs +341 -0
- package/scripts/sync-transitions.mjs +146 -0
- package/scripts/validate-apple-symbol-exports.mjs +45 -0
- package/src/agents/agent-avatar.svelte +639 -0
- package/src/agents/agent-cast.svelte +173 -0
- package/src/agents/demo.svelte +78 -0
- package/src/agents/index.ts +11 -0
- package/src/agents/types.ts +72 -0
- package/src/agents/vendor/LICENSE +21 -0
- package/src/agents/vendor/color.ts +155 -0
- package/src/agents/vendor/draw.ts +840 -0
- package/src/agents/vendor/engine.ts +845 -0
- package/src/agents/vendor/mesh.ts +838 -0
- package/src/agents/vendor/plastic.ts +4958 -0
- package/src/agents/vendor/presets.ts +103 -0
- package/src/agents/vendor/shapes.ts +41 -0
- package/src/agents/vendor/ticker.ts +67 -0
- package/src/agents/vendor/types.ts +261 -0
- package/src/agents/vendor/wear.ts +1699 -0
- package/src/ai/composer/Composer.css +108 -0
- package/src/ai/composer/Composer.svelte +111 -0
- package/src/ai/composer/index.ts +10 -0
- package/src/ai/confirmation/Confirmation.svelte +54 -0
- package/src/ai/confirmation/ConfirmationAccepted.svelte +23 -0
- package/src/ai/confirmation/ConfirmationAction.svelte +53 -0
- package/src/ai/confirmation/ConfirmationActions.svelte +24 -0
- package/src/ai/confirmation/ConfirmationFailed.svelte +23 -0
- package/src/ai/confirmation/ConfirmationRejected.svelte +23 -0
- package/src/ai/confirmation/ConfirmationRequest.svelte +25 -0
- package/src/ai/confirmation/ConfirmationTitle.svelte +25 -0
- package/src/ai/confirmation/confirmation-context.svelte.ts +22 -0
- package/src/ai/confirmation/index.ts +50 -0
- package/src/ai/conversation/Conversation.svelte +28 -0
- package/src/ai/conversation/ConversationContent.svelte +35 -0
- package/src/ai/conversation/ConversationEmptyState.svelte +46 -0
- package/src/ai/conversation/ConversationScrollButton.svelte +51 -0
- package/src/ai/conversation/index.ts +33 -0
- package/src/ai/conversation/stick-to-bottom-context.svelte.ts +131 -0
- package/src/ai/index.ts +119 -0
- package/src/ai/loading-state/PixelLoader.svelte +138 -0
- package/src/ai/loading-state/index.ts +5 -0
- package/src/ai/prompt-input/PromptInput.svelte +38 -0
- package/src/ai/prompt-input/PromptInputBody.svelte +19 -0
- package/src/ai/prompt-input/PromptInputSubmit.svelte +73 -0
- package/src/ai/prompt-input/PromptInputTextarea.svelte +74 -0
- package/src/ai/prompt-input/PromptInputToolbar.svelte +19 -0
- package/src/ai/prompt-input/index.ts +34 -0
- package/src/ai/reasoning/Reasoning.svelte +47 -0
- package/src/ai/reasoning/ReasoningContent.svelte +27 -0
- package/src/ai/reasoning/ReasoningTrigger.svelte +48 -0
- package/src/ai/reasoning/index.ts +22 -0
- package/src/ai/reasoning/reasoning-context.svelte.ts +20 -0
- package/src/ai/response/Response.css +32 -0
- package/src/ai/response/Response.svelte +130 -0
- package/src/ai/response/index.ts +5 -0
- package/src/ai/suggestion/Suggestion.svelte +43 -0
- package/src/ai/suggestion/Suggestions.svelte +25 -0
- package/src/ai/suggestion/index.ts +14 -0
- package/src/ai/thinking-dots/ThinkingDots.svelte +42 -0
- package/src/ai/thinking-dots/index.ts +5 -0
- package/src/ai/typing-dots/TypingDots.css +39 -0
- package/src/ai/typing-dots/TypingDots.svelte +27 -0
- package/src/ai/typing-dots/index.ts +10 -0
- package/src/ai/voice-waveform/VoiceWaveform.css +40 -0
- package/src/ai/voice-waveform/VoiceWaveform.svelte +46 -0
- package/src/ai/voice-waveform/index.ts +5 -0
- package/src/animations/README.md +84 -0
- package/src/animations/accordion.css +58 -0
- package/src/animations/avatar-group-hover.css +47 -0
- package/src/animations/card-tilt.css +74 -0
- package/src/animations/digit-pop-in.css +78 -0
- package/src/animations/dropdown.css +121 -0
- package/src/animations/icon-swap.css +43 -0
- package/src/animations/index.css +34 -0
- package/src/animations/input-clear-dissolve.css +86 -0
- package/src/animations/input-shake.css +114 -0
- package/src/animations/learn-more-hover.css +48 -0
- package/src/animations/like-button.css +93 -0
- package/src/animations/modal.css +75 -0
- package/src/animations/notification-badge.css +81 -0
- package/src/animations/page-side-by-side.css +63 -0
- package/src/animations/panel.css +86 -0
- package/src/animations/plus-menu-morph.css +97 -0
- package/src/animations/reasoning-stream.css +49 -0
- package/src/animations/resize.css +20 -0
- package/src/animations/shimmer-text.css +72 -0
- package/src/animations/skeleton-reveal.css +97 -0
- package/src/animations/spinning-counter.css +52 -0
- package/src/animations/streaming-text.css +31 -0
- package/src/animations/success-check.css +120 -0
- package/src/animations/tabs-sliding.css +83 -0
- package/src/animations/text-swap.css +40 -0
- package/src/animations/text-swap.ts +40 -0
- package/src/animations/texts-reveal.css +70 -0
- package/src/animations/thinking-states.css +65 -0
- package/src/animations/tooltip.css +72 -0
- package/src/animations/upstream-coverage.test.ts +65 -0
- package/src/animations/view-transition-reveal.ts +74 -0
- package/src/charts/animated-value.css +7 -0
- package/src/charts/animated-value.svelte +110 -0
- package/src/charts/area-chart.css +29 -0
- package/src/charts/area-chart.svelte +195 -0
- package/src/charts/bar-chart.css +119 -0
- package/src/charts/bar-chart.svelte +269 -0
- package/src/charts/charts.css +201 -0
- package/src/charts/crosshair.svelte +35 -0
- package/src/charts/donut-chart.css +56 -0
- package/src/charts/donut-chart.svelte +158 -0
- package/src/charts/edge-fade.svelte +43 -0
- package/src/charts/edge-fade.ts +16 -0
- package/src/charts/figure.css +100 -0
- package/src/charts/figure.svelte +122 -0
- package/src/charts/gauge-chart.css +94 -0
- package/src/charts/gauge-chart.svelte +303 -0
- package/src/charts/hover-band.test.ts +30 -0
- package/src/charts/hover-band.ts +21 -0
- package/src/charts/hover-focus.svelte +85 -0
- package/src/charts/index.ts +30 -0
- package/src/charts/legend.css +131 -0
- package/src/charts/legend.svelte +112 -0
- package/src/charts/line-chart.css +26 -0
- package/src/charts/line-chart.svelte +256 -0
- package/src/charts/motion.ts +19 -0
- package/src/charts/number-chart.css +168 -0
- package/src/charts/number-chart.svelte +177 -0
- package/src/charts/primitives/chart-container.svelte +57 -0
- package/src/charts/primitives/chart-style.svelte +36 -0
- package/src/charts/primitives/chart-tooltip.svelte +161 -0
- package/src/charts/primitives/chart-utils.ts +54 -0
- package/src/charts/primitives/index.ts +11 -0
- package/src/charts/reveal.svelte +61 -0
- package/src/charts/ring-chart.css +96 -0
- package/src/charts/ring-chart.svelte +158 -0
- package/src/charts/sparkline.css +45 -0
- package/src/charts/sparkline.svelte +140 -0
- package/src/charts/stat-card.css +103 -0
- package/src/charts/stat-card.svelte +267 -0
- package/src/charts/time-series-chart.css +28 -0
- package/src/charts/time-series-chart.svelte +179 -0
- package/src/charts/trend-pill.css +38 -0
- package/src/charts/trend-pill.svelte +48 -0
- package/src/design/index.css +270 -0
- package/src/design/polish.css +159 -0
- package/src/effects/animated-beam/AnimatedBeam.svelte +132 -0
- package/src/effects/animated-beam/diagrams/BeamSvg.svelte +91 -0
- package/src/effects/animated-beam/index.ts +2 -0
- package/src/effects/animated-beam/path-calculator.svelte.ts +70 -0
- package/src/effects/animated-beam/types.ts +23 -0
- package/src/effects/border-beam/BorderBeam.svelte +223 -0
- package/src/effects/border-beam/css.test.ts +46 -0
- package/src/effects/border-beam/css.ts +574 -0
- package/src/effects/border-beam/presets.ts +699 -0
- package/src/effects/border-beam/register-properties.test.ts +74 -0
- package/src/effects/border-beam/register-properties.ts +73 -0
- package/src/effects/border-beam/types.ts +38 -0
- package/src/effects/image-comparison/ImageComparison.css +66 -0
- package/src/effects/image-comparison/ImageComparison.svelte +92 -0
- package/src/effects/image-reveal/ImageReveal.css +10 -0
- package/src/effects/image-reveal/ImageReveal.svelte +357 -0
- package/src/effects/image-reveal/index.ts +2 -0
- package/src/effects/image-reveal/types.ts +35 -0
- package/src/effects/magnetic/magnetic.ts +81 -0
- package/src/effects/marquee/Marquee.css +52 -0
- package/src/effects/marquee/Marquee.svelte +96 -0
- package/src/effects/progressive-blur/ProgressiveBlur.css +9 -0
- package/src/effects/progressive-blur/ProgressiveBlur.svelte +63 -0
- package/src/effects/reveal/AnimatedGroup.svelte +101 -0
- package/src/effects/reveal/InView.svelte +77 -0
- package/src/effects/reveal/TextEffect.svelte +142 -0
- package/src/effects/reveal/index.ts +10 -0
- package/src/effects/reveal/observe.ts +33 -0
- package/src/effects/reveal/reveal.css +141 -0
- package/src/effects/reveal/types.ts +25 -0
- package/src/effects/shader-gradient/ShaderGradient.svelte +227 -0
- package/src/effects/shader-gradient/color.ts +44 -0
- package/src/effects/shader-gradient/index.ts +5 -0
- package/src/effects/shader-gradient/renderer.ts +456 -0
- package/src/effects/shader-gradient/shaders.ts +324 -0
- package/src/effects/shader-gradient/types.ts +68 -0
- package/src/effects/spotlight/Spotlight.css +14 -0
- package/src/effects/spotlight/Spotlight.svelte +61 -0
- package/src/effects/theme-toggle/ThemeToggle.css +21 -0
- package/src/effects/theme-toggle/ThemeToggle.svelte +101 -0
- package/src/icons/TradeIcon.svelte +16 -0
- package/src/icons/apple-symbol-exports.ts +135 -0
- package/src/icons/sf-symbols.ts +527 -0
- package/src/icons/trades.ts +89 -0
- package/src/index.ts +49 -0
- package/src/lib/arrive.ts +26 -0
- package/src/lib/hooks/is-mobile.svelte.ts +11 -0
- package/src/lib/icon-types.ts +17 -0
- package/src/lib/layout.ts +8 -0
- package/src/lib/panel.ts +15 -0
- package/src/lib/theme-transition.ts +51 -0
- package/src/lib/utils.ts +45 -0
- package/src/shadcn/accordion/accordion-content.svelte +22 -0
- package/src/shadcn/accordion/accordion-item.svelte +17 -0
- package/src/shadcn/accordion/accordion-trigger.svelte +35 -0
- package/src/shadcn/accordion/accordion.svelte +19 -0
- package/src/shadcn/accordion/index.ts +16 -0
- package/src/shadcn/alert/alert-description.svelte +23 -0
- package/src/shadcn/alert/alert-title.svelte +20 -0
- package/src/shadcn/alert/alert.svelte +44 -0
- package/src/shadcn/alert/index.ts +14 -0
- package/src/shadcn/alert-dialog/alert-dialog-action.svelte +18 -0
- package/src/shadcn/alert-dialog/alert-dialog-cancel.svelte +18 -0
- package/src/shadcn/alert-dialog/alert-dialog-content.svelte +29 -0
- package/src/shadcn/alert-dialog/alert-dialog-description.svelte +17 -0
- package/src/shadcn/alert-dialog/alert-dialog-footer.svelte +20 -0
- package/src/shadcn/alert-dialog/alert-dialog-header.svelte +20 -0
- package/src/shadcn/alert-dialog/alert-dialog-overlay.svelte +17 -0
- package/src/shadcn/alert-dialog/alert-dialog-portal.svelte +7 -0
- package/src/shadcn/alert-dialog/alert-dialog-title.svelte +17 -0
- package/src/shadcn/alert-dialog/alert-dialog-trigger.svelte +7 -0
- package/src/shadcn/alert-dialog/alert-dialog.svelte +7 -0
- package/src/shadcn/alert-dialog/index.ts +37 -0
- package/src/shadcn/attachment/attachment-action.svelte +23 -0
- package/src/shadcn/attachment/attachment-actions.svelte +23 -0
- package/src/shadcn/attachment/attachment-content.svelte +23 -0
- package/src/shadcn/attachment/attachment-description.svelte +23 -0
- package/src/shadcn/attachment/attachment-group.svelte +23 -0
- package/src/shadcn/attachment/attachment-media.svelte +44 -0
- package/src/shadcn/attachment/attachment-title.svelte +23 -0
- package/src/shadcn/attachment/attachment-trigger.svelte +29 -0
- package/src/shadcn/attachment/attachment.svelte +55 -0
- package/src/shadcn/attachment/index.ts +40 -0
- package/src/shadcn/avatar/avatar-badge.svelte +26 -0
- package/src/shadcn/avatar/avatar-fallback.svelte +20 -0
- package/src/shadcn/avatar/avatar-group-count.svelte +23 -0
- package/src/shadcn/avatar/avatar-group.svelte +23 -0
- package/src/shadcn/avatar/avatar-image.svelte +17 -0
- package/src/shadcn/avatar/avatar.svelte +26 -0
- package/src/shadcn/avatar/index.ts +22 -0
- package/src/shadcn/badge/badge.svelte +53 -0
- package/src/shadcn/badge/index.ts +2 -0
- package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +23 -0
- package/src/shadcn/breadcrumb/breadcrumb-item.svelte +20 -0
- package/src/shadcn/breadcrumb/breadcrumb-link.svelte +31 -0
- package/src/shadcn/breadcrumb/breadcrumb-list.svelte +23 -0
- package/src/shadcn/breadcrumb/breadcrumb-page.svelte +23 -0
- package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +27 -0
- package/src/shadcn/breadcrumb/breadcrumb.svelte +21 -0
- package/src/shadcn/breadcrumb/index.ts +25 -0
- package/src/shadcn/bubble/bubble-content.svelte +31 -0
- package/src/shadcn/bubble/bubble-group.svelte +20 -0
- package/src/shadcn/bubble/bubble-reactions.svelte +52 -0
- package/src/shadcn/bubble/bubble.svelte +58 -0
- package/src/shadcn/bubble/index.ts +25 -0
- package/src/shadcn/button/button.svelte +121 -0
- package/src/shadcn/button/index.ts +17 -0
- package/src/shadcn/calendar/calendar-caption.svelte +76 -0
- package/src/shadcn/calendar/calendar-cell.svelte +19 -0
- package/src/shadcn/calendar/calendar-day.svelte +35 -0
- package/src/shadcn/calendar/calendar-grid-body.svelte +12 -0
- package/src/shadcn/calendar/calendar-grid-head.svelte +12 -0
- package/src/shadcn/calendar/calendar-grid-row.svelte +12 -0
- package/src/shadcn/calendar/calendar-grid.svelte +16 -0
- package/src/shadcn/calendar/calendar-head-cell.svelte +19 -0
- package/src/shadcn/calendar/calendar-header.svelte +19 -0
- package/src/shadcn/calendar/calendar-heading.svelte +16 -0
- package/src/shadcn/calendar/calendar-month-select.svelte +44 -0
- package/src/shadcn/calendar/calendar-month.svelte +15 -0
- package/src/shadcn/calendar/calendar-months.svelte +19 -0
- package/src/shadcn/calendar/calendar-nav.svelte +19 -0
- package/src/shadcn/calendar/calendar-next-button.svelte +31 -0
- package/src/shadcn/calendar/calendar-prev-button.svelte +31 -0
- package/src/shadcn/calendar/calendar-year-select.svelte +43 -0
- package/src/shadcn/calendar/calendar.svelte +118 -0
- package/src/shadcn/calendar/index.ts +40 -0
- package/src/shadcn/card/card-action.svelte +23 -0
- package/src/shadcn/card/card-content.svelte +20 -0
- package/src/shadcn/card/card-description.svelte +20 -0
- package/src/shadcn/card/card-footer.svelte +23 -0
- package/src/shadcn/card/card-header.svelte +23 -0
- package/src/shadcn/card/card-title.svelte +29 -0
- package/src/shadcn/card/card.svelte +25 -0
- package/src/shadcn/card/index.ts +25 -0
- package/src/shadcn/checkbox/checkbox.svelte +38 -0
- package/src/shadcn/checkbox/index.ts +6 -0
- package/src/shadcn/collapsible/collapsible-content.svelte +7 -0
- package/src/shadcn/collapsible/collapsible-trigger.svelte +7 -0
- package/src/shadcn/collapsible/collapsible.svelte +11 -0
- package/src/shadcn/collapsible/index.ts +13 -0
- package/src/shadcn/command/command-dialog.svelte +66 -0
- package/src/shadcn/command/command-empty.svelte +17 -0
- package/src/shadcn/command/command-group.svelte +40 -0
- package/src/shadcn/command/command-input.svelte +64 -0
- package/src/shadcn/command/command-item.svelte +37 -0
- package/src/shadcn/command/command-link-item.svelte +20 -0
- package/src/shadcn/command/command-list.svelte +20 -0
- package/src/shadcn/command/command-loading.svelte +7 -0
- package/src/shadcn/command/command-separator.svelte +17 -0
- package/src/shadcn/command/command-shortcut.svelte +23 -0
- package/src/shadcn/command/command.svelte +28 -0
- package/src/shadcn/command/index.ts +37 -0
- package/src/shadcn/context-menu/context-menu-checkbox-item.svelte +41 -0
- package/src/shadcn/context-menu/context-menu-content.svelte +28 -0
- package/src/shadcn/context-menu/context-menu-group-heading.svelte +21 -0
- package/src/shadcn/context-menu/context-menu-group.svelte +7 -0
- package/src/shadcn/context-menu/context-menu-item.svelte +27 -0
- package/src/shadcn/context-menu/context-menu-label.svelte +27 -0
- package/src/shadcn/context-menu/context-menu-portal.svelte +7 -0
- package/src/shadcn/context-menu/context-menu-radio-group.svelte +16 -0
- package/src/shadcn/context-menu/context-menu-radio-item.svelte +35 -0
- package/src/shadcn/context-menu/context-menu-separator.svelte +17 -0
- package/src/shadcn/context-menu/context-menu-shortcut.svelte +23 -0
- package/src/shadcn/context-menu/context-menu-sub-content.svelte +25 -0
- package/src/shadcn/context-menu/context-menu-sub-trigger.svelte +29 -0
- package/src/shadcn/context-menu/context-menu-sub.svelte +7 -0
- package/src/shadcn/context-menu/context-menu-trigger.svelte +17 -0
- package/src/shadcn/context-menu/context-menu.svelte +7 -0
- package/src/shadcn/context-menu/index.ts +52 -0
- package/src/shadcn/dialog/dialog-close.svelte +11 -0
- package/src/shadcn/dialog/dialog-content.svelte +57 -0
- package/src/shadcn/dialog/dialog-description.svelte +20 -0
- package/src/shadcn/dialog/dialog-footer.svelte +32 -0
- package/src/shadcn/dialog/dialog-header.svelte +20 -0
- package/src/shadcn/dialog/dialog-overlay.svelte +20 -0
- package/src/shadcn/dialog/dialog-portal.svelte +7 -0
- package/src/shadcn/dialog/dialog-title.svelte +17 -0
- package/src/shadcn/dialog/dialog-trigger.svelte +11 -0
- package/src/shadcn/dialog/dialog.svelte +7 -0
- package/src/shadcn/dialog/index.ts +34 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +31 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-item.svelte +29 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-label.svelte +27 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-radio-item.svelte +34 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-shortcut.svelte +23 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/src/shadcn/dropdown-menu/dropdown-menu.svelte +7 -0
- package/src/shadcn/dropdown-menu/index.ts +56 -0
- package/src/shadcn/dropdown-menu/menu-item.ts +43 -0
- package/src/shadcn/empty/empty-content.svelte +23 -0
- package/src/shadcn/empty/empty-description.svelte +23 -0
- package/src/shadcn/empty/empty-header.svelte +20 -0
- package/src/shadcn/empty/empty-media.svelte +41 -0
- package/src/shadcn/empty/empty-title.svelte +20 -0
- package/src/shadcn/empty/empty.svelte +23 -0
- package/src/shadcn/empty/index.ts +22 -0
- package/src/shadcn/field/field-content.svelte +20 -0
- package/src/shadcn/field/field-description.svelte +25 -0
- package/src/shadcn/field/field-error.svelte +58 -0
- package/src/shadcn/field/field-group.svelte +23 -0
- package/src/shadcn/field/field-label.svelte +26 -0
- package/src/shadcn/field/field-legend.svelte +29 -0
- package/src/shadcn/field/field-separator.svelte +35 -0
- package/src/shadcn/field/field-set.svelte +24 -0
- package/src/shadcn/field/field-title.svelte +23 -0
- package/src/shadcn/field/field.svelte +53 -0
- package/src/shadcn/field/index.ts +33 -0
- package/src/shadcn/hooks/is-mobile.svelte.ts +9 -0
- package/src/shadcn/hover-card/hover-card-content.svelte +31 -0
- package/src/shadcn/hover-card/hover-card-portal.svelte +7 -0
- package/src/shadcn/hover-card/hover-card-trigger.svelte +7 -0
- package/src/shadcn/hover-card/hover-card.svelte +7 -0
- package/src/shadcn/hover-card/index.ts +15 -0
- package/src/shadcn/input/index.ts +9 -0
- package/src/shadcn/input/input.svelte +48 -0
- package/src/shadcn/input/secret-input.svelte +75 -0
- package/src/shadcn/input-otp/index.ts +15 -0
- package/src/shadcn/input-otp/input-otp-group.svelte +20 -0
- package/src/shadcn/input-otp/input-otp-separator.svelte +25 -0
- package/src/shadcn/input-otp/input-otp-slot.svelte +26 -0
- package/src/shadcn/input-otp/input-otp.svelte +22 -0
- package/src/shadcn/label/index.ts +7 -0
- package/src/shadcn/label/label.svelte +20 -0
- package/src/shadcn/marker/index.ts +15 -0
- package/src/shadcn/marker/marker-content.svelte +23 -0
- package/src/shadcn/marker/marker-icon.svelte +21 -0
- package/src/shadcn/marker/marker.svelte +52 -0
- package/src/shadcn/message/index.ts +22 -0
- package/src/shadcn/message/message-avatar.svelte +23 -0
- package/src/shadcn/message/message-content.svelte +23 -0
- package/src/shadcn/message/message-footer.svelte +23 -0
- package/src/shadcn/message/message-group.svelte +20 -0
- package/src/shadcn/message/message-header.svelte +23 -0
- package/src/shadcn/message/message.svelte +27 -0
- package/src/shadcn/popover/index.ts +28 -0
- package/src/shadcn/popover/popover-close.svelte +7 -0
- package/src/shadcn/popover/popover-content.svelte +38 -0
- package/src/shadcn/popover/popover-description.svelte +20 -0
- package/src/shadcn/popover/popover-header.svelte +20 -0
- package/src/shadcn/popover/popover-portal.svelte +7 -0
- package/src/shadcn/popover/popover-title.svelte +15 -0
- package/src/shadcn/popover/popover-trigger.svelte +17 -0
- package/src/shadcn/popover/popover.svelte +7 -0
- package/src/shadcn/progress/index.ts +7 -0
- package/src/shadcn/progress/progress.svelte +30 -0
- package/src/shadcn/radio-group/index.ts +10 -0
- package/src/shadcn/radio-group/radio-group-item.svelte +31 -0
- package/src/shadcn/radio-group/radio-group.svelte +19 -0
- package/src/shadcn/scroll-area/index.ts +10 -0
- package/src/shadcn/scroll-area/scroll-area-scrollbar.svelte +30 -0
- package/src/shadcn/scroll-area/scroll-area.svelte +43 -0
- package/src/shadcn/select/index.ts +37 -0
- package/src/shadcn/select/select-content.svelte +45 -0
- package/src/shadcn/select/select-group-heading.svelte +21 -0
- package/src/shadcn/select/select-group.svelte +17 -0
- package/src/shadcn/select/select-item.svelte +38 -0
- package/src/shadcn/select/select-label.svelte +20 -0
- package/src/shadcn/select/select-portal.svelte +7 -0
- package/src/shadcn/select/select-scroll-down-button.svelte +23 -0
- package/src/shadcn/select/select-scroll-up-button.svelte +23 -0
- package/src/shadcn/select/select-separator.svelte +18 -0
- package/src/shadcn/select/select-trigger.svelte +33 -0
- package/src/shadcn/select/select.svelte +11 -0
- package/src/shadcn/separator/index.ts +7 -0
- package/src/shadcn/separator/separator.svelte +23 -0
- package/src/shadcn/sheet/index.ts +34 -0
- package/src/shadcn/sheet/sheet-close.svelte +7 -0
- package/src/shadcn/sheet/sheet-content.svelte +72 -0
- package/src/shadcn/sheet/sheet-description.svelte +17 -0
- package/src/shadcn/sheet/sheet-footer.svelte +20 -0
- package/src/shadcn/sheet/sheet-header.svelte +20 -0
- package/src/shadcn/sheet/sheet-overlay.svelte +20 -0
- package/src/shadcn/sheet/sheet-portal.svelte +7 -0
- package/src/shadcn/sheet/sheet-title.svelte +17 -0
- package/src/shadcn/sheet/sheet-trigger.svelte +7 -0
- package/src/shadcn/sheet/sheet.svelte +7 -0
- package/src/shadcn/sidebar/constants.ts +6 -0
- package/src/shadcn/sidebar/context.svelte.ts +81 -0
- package/src/shadcn/sidebar/index.ts +75 -0
- package/src/shadcn/sidebar/sidebar-content.svelte +24 -0
- package/src/shadcn/sidebar/sidebar-footer.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-group-action.svelte +33 -0
- package/src/shadcn/sidebar/sidebar-group-content.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-group-label.svelte +33 -0
- package/src/shadcn/sidebar/sidebar-group.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-header.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-input.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-inset.svelte +23 -0
- package/src/shadcn/sidebar/sidebar-menu-action.svelte +37 -0
- package/src/shadcn/sidebar/sidebar-menu-badge.svelte +24 -0
- package/src/shadcn/sidebar/sidebar-menu-button.svelte +103 -0
- package/src/shadcn/sidebar/sidebar-menu-item.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +39 -0
- package/src/shadcn/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-menu-sub.svelte +24 -0
- package/src/shadcn/sidebar/sidebar-menu.svelte +21 -0
- package/src/shadcn/sidebar/sidebar-provider.svelte +62 -0
- package/src/shadcn/sidebar/sidebar-rail.svelte +36 -0
- package/src/shadcn/sidebar/sidebar-separator.svelte +19 -0
- package/src/shadcn/sidebar/sidebar-trigger.svelte +37 -0
- package/src/shadcn/sidebar/sidebar.svelte +109 -0
- package/src/shadcn/skeleton/index.ts +7 -0
- package/src/shadcn/skeleton/skeleton.svelte +17 -0
- package/src/shadcn/slider/index.ts +7 -0
- package/src/shadcn/slider/slider.svelte +52 -0
- package/src/shadcn/sonner/index.ts +1 -0
- package/src/shadcn/sonner/sonner.svelte +34 -0
- package/src/shadcn/spinner/index.ts +1 -0
- package/src/shadcn/spinner/spinner.svelte +26 -0
- package/src/shadcn/switch/index.ts +7 -0
- package/src/shadcn/switch/switch.svelte +31 -0
- package/src/shadcn/table/index.ts +28 -0
- package/src/shadcn/table/table-body.svelte +20 -0
- package/src/shadcn/table/table-caption.svelte +20 -0
- package/src/shadcn/table/table-cell.svelte +20 -0
- package/src/shadcn/table/table-footer.svelte +20 -0
- package/src/shadcn/table/table-head.svelte +23 -0
- package/src/shadcn/table/table-header.svelte +20 -0
- package/src/shadcn/table/table-row.svelte +23 -0
- package/src/shadcn/table/table.svelte +25 -0
- package/src/shadcn/tabs/index.ts +16 -0
- package/src/shadcn/tabs/tabs-content.svelte +17 -0
- package/src/shadcn/tabs/tabs-list.svelte +25 -0
- package/src/shadcn/tabs/tabs-trigger.svelte +20 -0
- package/src/shadcn/tabs/tabs.svelte +19 -0
- package/src/shadcn/textarea/index.ts +7 -0
- package/src/shadcn/textarea/textarea.svelte +23 -0
- package/src/shadcn/toggle/index.ts +13 -0
- package/src/shadcn/toggle/toggle.svelte +53 -0
- package/src/shadcn/toggle-group/index.ts +10 -0
- package/src/shadcn/toggle-group/toggle-group-item.svelte +35 -0
- package/src/shadcn/toggle-group/toggle-group.svelte +59 -0
- package/src/shadcn/tooltip/context.ts +16 -0
- package/src/shadcn/tooltip/index.ts +19 -0
- package/src/shadcn/tooltip/tooltip-content.svelte +38 -0
- package/src/shadcn/tooltip/tooltip-portal.svelte +7 -0
- package/src/shadcn/tooltip/tooltip-provider.svelte +16 -0
- package/src/shadcn/tooltip/tooltip-trigger.svelte +7 -0
- package/src/shadcn/tooltip/tooltip.svelte +19 -0
- package/src/shadcn/utilities.css +549 -0
- package/src/showcase/AiSection.svelte +157 -0
- package/src/showcase/AnimationsSection.svelte +1658 -0
- package/src/showcase/ChartsSection.svelte +458 -0
- package/src/showcase/EffectsSection.svelte +152 -0
- package/src/showcase/ShadcnSection.svelte +27 -0
- package/src/showcase/ShowcaseImport.svelte +126 -0
- package/src/showcase/ShowcaseList.svelte +132 -0
- package/src/showcase/ShowcaseNav.svelte +247 -0
- package/src/showcase/ShowcaseSection.svelte +112 -0
- package/src/showcase/ShowcaseShell.svelte +55 -0
- package/src/showcase/ShowcaseStage.svelte +342 -0
- package/src/showcase/ShowcaseVariant.svelte +109 -0
- package/src/showcase/assets/image-reveal/art-1.jpg +0 -0
- package/src/showcase/assets/image-reveal/art-2.jpg +0 -0
- package/src/showcase/assets/image-reveal/art-3.jpg +0 -0
- package/src/showcase/check.svg +1 -0
- package/src/showcase/chevron-arms.svg +2 -0
- package/src/showcase/chevron-down.svg +1 -0
- package/src/showcase/copy.svg +2 -0
- package/src/showcase/demos/ai/confirmation.svelte +59 -0
- package/src/showcase/demos/ai/conversation.svelte +59 -0
- package/src/showcase/demos/ai/loading-state.svelte +19 -0
- package/src/showcase/demos/ai/prompt-input.svelte +57 -0
- package/src/showcase/demos/ai/reasoning.svelte +19 -0
- package/src/showcase/demos/ai/thinking-dots.svelte +15 -0
- package/src/showcase/demos/ai/voice-waveform.svelte +24 -0
- package/src/showcase/demos/effects/animated-beam.svelte +50 -0
- package/src/showcase/demos/effects/animated-group.svelte +47 -0
- package/src/showcase/demos/effects/border-beam-default.svelte +20 -0
- package/src/showcase/demos/effects/border-beam-line.svelte +20 -0
- package/src/showcase/demos/effects/border-beam-mono.svelte +20 -0
- package/src/showcase/demos/effects/border-beam-sunset.svelte +20 -0
- package/src/showcase/demos/effects/image-comparison.svelte +25 -0
- package/src/showcase/demos/effects/image-reveal.svelte +49 -0
- package/src/showcase/demos/effects/in-view.svelte +41 -0
- package/src/showcase/demos/effects/integrations-hub.svelte +82 -0
- package/src/showcase/demos/effects/magnetic.svelte +31 -0
- package/src/showcase/demos/effects/marquee.svelte +33 -0
- package/src/showcase/demos/effects/progressive-blur.svelte +47 -0
- package/src/showcase/demos/effects/spotlight.svelte +40 -0
- package/src/showcase/demos/effects/text-effect.svelte +55 -0
- package/src/showcase/demos/shadcn/accordion.svelte +45 -0
- package/src/showcase/demos/shadcn/alert-dialog.svelte +23 -0
- package/src/showcase/demos/shadcn/alert.svelte +30 -0
- package/src/showcase/demos/shadcn/attachment.svelte +71 -0
- package/src/showcase/demos/shadcn/avatar.svelte +30 -0
- package/src/showcase/demos/shadcn/badge.svelte +26 -0
- package/src/showcase/demos/shadcn/breadcrumb.svelte +34 -0
- package/src/showcase/demos/shadcn/bubble.svelte +36 -0
- package/src/showcase/demos/shadcn/button.svelte +11 -0
- package/src/showcase/demos/shadcn/calendar.svelte +8 -0
- package/src/showcase/demos/shadcn/card.svelte +39 -0
- package/src/showcase/demos/shadcn/checkbox.svelte +39 -0
- package/src/showcase/demos/shadcn/collapsible.svelte +20 -0
- package/src/showcase/demos/shadcn/command.svelte +48 -0
- package/src/showcase/demos/shadcn/context-menu.svelte +54 -0
- package/src/showcase/demos/shadcn/demo-harness.svelte +12 -0
- package/src/showcase/demos/shadcn/demos.test.ts +20 -0
- package/src/showcase/demos/shadcn/dialog.svelte +38 -0
- package/src/showcase/demos/shadcn/dropdown-menu.svelte +59 -0
- package/src/showcase/demos/shadcn/empty.svelte +29 -0
- package/src/showcase/demos/shadcn/field.svelte +115 -0
- package/src/showcase/demos/shadcn/hover-card.svelte +32 -0
- package/src/showcase/demos/shadcn/index.ts +358 -0
- package/src/showcase/demos/shadcn/input-otp.svelte +19 -0
- package/src/showcase/demos/shadcn/input.svelte +5 -0
- package/src/showcase/demos/shadcn/label.svelte +11 -0
- package/src/showcase/demos/shadcn/marker.svelte +30 -0
- package/src/showcase/demos/shadcn/message.svelte +75 -0
- package/src/showcase/demos/shadcn/popover.svelte +36 -0
- package/src/showcase/demos/shadcn/progress.svelte +13 -0
- package/src/showcase/demos/shadcn/radio-group.svelte +19 -0
- package/src/showcase/demos/shadcn/scroll-area.svelte +18 -0
- package/src/showcase/demos/shadcn/select.svelte +29 -0
- package/src/showcase/demos/shadcn/separator.svelte +12 -0
- package/src/showcase/demos/shadcn/sheet.svelte +48 -0
- package/src/showcase/demos/shadcn/skeleton.svelte +11 -0
- package/src/showcase/demos/shadcn/slider.svelte +7 -0
- package/src/showcase/demos/shadcn/sonner.svelte +18 -0
- package/src/showcase/demos/shadcn/spinner.svelte +11 -0
- package/src/showcase/demos/shadcn/switch.svelte +9 -0
- package/src/showcase/demos/shadcn/table.svelte +76 -0
- package/src/showcase/demos/shadcn/tabs.svelte +62 -0
- package/src/showcase/demos/shadcn/textarea.svelte +5 -0
- package/src/showcase/demos/shadcn/toggle-group.svelte +18 -0
- package/src/showcase/demos/shadcn/toggle.svelte +14 -0
- package/src/showcase/demos/shadcn/tooltip.svelte +13 -0
- package/src/showcase/heart.svg +1 -0
- package/src/showcase/index.ts +38 -0
- package/src/showcase/kit.test.ts +72 -0
- package/src/showcase/kit.ts +17 -0
- package/src/showcase/plus.svg +1 -0
- package/src/showcase/showcase-shell.css +91 -0
- package/src/showcase/stacked-section.css +15 -0
- package/src/showcase/success-check.svg +1 -0
- package/src/showcase/types.ts +29 -0
- package/src/svg-raw.d.ts +7 -0
- package/src/test-setup.ts +34 -0
- package/src/tokens.css +993 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
// @ts-nocheck WHY: vendored engine, kept as upstream wrote it so a re-sync stays a clean diff. Strict flags in consumers (review) would flag ~870 spots.
|
|
2
|
+
/* mesh.ts — a small surface renderer for the things the bots wear.
|
|
3
|
+
|
|
4
|
+
A worn object is a handful of parametric surfaces (a dome, a band, a
|
|
5
|
+
cone, a tube along a curve, a revolved profile), each sampled into a
|
|
6
|
+
grid in its own space once and kept; every frame the grid is projected
|
|
7
|
+
through the head's rotation and lit from the body's light with a
|
|
8
|
+
material: felt, knit, paper, gold, silver, velvet, satin, soft plastic,
|
|
9
|
+
leather, pearl, a cut gem.
|
|
10
|
+
|
|
11
|
+
It is painted a strip at a time: the visible stretch of each row of the
|
|
12
|
+
grid is one shape, filled with a gradient that carries the light along
|
|
13
|
+
the row from the corners' own normals, so a dome is a dozen fills rather
|
|
14
|
+
than hundreds of quads and its light runs smoothly round it; the rows
|
|
15
|
+
step it the other way, finely enough not to show. Strips go back to
|
|
16
|
+
front across all the surfaces of an object. Cut facets are painted a
|
|
17
|
+
cell at a time, flat. Small spheres — pearls, beads, rivets — are
|
|
18
|
+
single radial fills. Canvas 2D only. */
|
|
19
|
+
|
|
20
|
+
export type V3 = [number, number, number];
|
|
21
|
+
export type P2 = [number, number];
|
|
22
|
+
export type RGB = [number, number, number];
|
|
23
|
+
|
|
24
|
+
/** How the worn object is seen: a linear map from the object's space
|
|
25
|
+
(about the origin `o`) to view space — rows for screen x (right), screen
|
|
26
|
+
y (down) and depth (toward the viewer) — the light, and the scale. */
|
|
27
|
+
export interface View {
|
|
28
|
+
m: number[];
|
|
29
|
+
o: V3;
|
|
30
|
+
toScreen(p: V3): P2;
|
|
31
|
+
depth(p: V3): number;
|
|
32
|
+
/** a direction in the object's space, turned into view space, unit length */
|
|
33
|
+
dir(v: V3): V3;
|
|
34
|
+
/** toward the light, and the half vector with the view, in view space */
|
|
35
|
+
L: V3;
|
|
36
|
+
H: V3;
|
|
37
|
+
/** device pixels per unit of this space */
|
|
38
|
+
px: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function makeView(m: number[], o: V3, L: V3, H: V3, px: number): View {
|
|
42
|
+
return {
|
|
43
|
+
m,
|
|
44
|
+
o,
|
|
45
|
+
L,
|
|
46
|
+
H,
|
|
47
|
+
px,
|
|
48
|
+
toScreen: (p) => {
|
|
49
|
+
const x = p[0] - o[0],
|
|
50
|
+
y = p[1] - o[1],
|
|
51
|
+
z = p[2] - o[2];
|
|
52
|
+
return [m[0] * x + m[1] * y + m[2] * z, m[3] * x + m[4] * y + m[5] * z];
|
|
53
|
+
},
|
|
54
|
+
depth: (p) => m[6] * (p[0] - o[0]) + m[7] * (p[1] - o[1]) + m[8] * (p[2] - o[2]),
|
|
55
|
+
dir: (v) =>
|
|
56
|
+
norm([
|
|
57
|
+
m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
|
|
58
|
+
m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
|
|
59
|
+
m[6] * v[0] + m[7] * v[1] + m[8] * v[2],
|
|
60
|
+
]),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A material: a normal (view space) to a colour. */
|
|
65
|
+
export type Shade = (n: V3) => RGB;
|
|
66
|
+
|
|
67
|
+
export const sub = (a: V3, b: V3): V3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
68
|
+
export const cross = (a: V3, b: V3): V3 => [
|
|
69
|
+
a[1] * b[2] - a[2] * b[1],
|
|
70
|
+
a[2] * b[0] - a[0] * b[2],
|
|
71
|
+
a[0] * b[1] - a[1] * b[0],
|
|
72
|
+
];
|
|
73
|
+
export const dot = (a: V3, b: V3) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
74
|
+
export const norm = (v: V3): V3 => {
|
|
75
|
+
const l = Math.hypot(v[0], v[1], v[2]) || 1;
|
|
76
|
+
return [v[0] / l, v[1] / l, v[2] / l];
|
|
77
|
+
};
|
|
78
|
+
const TAU = Math.PI * 2;
|
|
79
|
+
|
|
80
|
+
/** How many segments to cut a length into for this view: about one per
|
|
81
|
+
`quad` device pixels, within `lo … hi`. */
|
|
82
|
+
export function segs(view: View, len: number, lo: number, hi: number, quad = 11): number {
|
|
83
|
+
return Math.max(lo, Math.min(hi, Math.round((len * view.px) / quad)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A surface sampled on a grid, in its object's space: positions, the
|
|
87
|
+
cells' normals and (for smooth shading) the corners'. Built once and
|
|
88
|
+
kept; each frame only projects it. */
|
|
89
|
+
export interface Mesh {
|
|
90
|
+
nu: number;
|
|
91
|
+
nv: number;
|
|
92
|
+
/** closed round u: its first and last columns are one */
|
|
93
|
+
closed: boolean;
|
|
94
|
+
pos: Float64Array;
|
|
95
|
+
fn: Float64Array;
|
|
96
|
+
vn: Float64Array | null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Sample `surf(u, v)` (u, v ∈ [0, 1]) on an `nu × nv` grid. `inside` is a
|
|
101
|
+
* point inside the object, or for a tube the point on its centre line:
|
|
102
|
+
* each normal is turned to face away from it, so a surface needs no care
|
|
103
|
+
* over its winding. With `smooth`, every corner gets the mean of its
|
|
104
|
+
* cells' normals — a surface closed round u, or pinched to a pole, is
|
|
105
|
+
* joined up — for the gradient fill; without, the cells stay faceted.
|
|
106
|
+
*/
|
|
107
|
+
export function makeMesh(
|
|
108
|
+
surf: (u: number, v: number) => V3,
|
|
109
|
+
nu: number,
|
|
110
|
+
nv: number,
|
|
111
|
+
inside: V3 | ((u: number, v: number) => V3),
|
|
112
|
+
smooth = true,
|
|
113
|
+
): Mesh {
|
|
114
|
+
const W = nu + 1,
|
|
115
|
+
N = W * (nv + 1);
|
|
116
|
+
const pos = new Float64Array(N * 3);
|
|
117
|
+
let ext = 0;
|
|
118
|
+
for (let j = 0, k = 0; j <= nv; j++) {
|
|
119
|
+
for (let i = 0; i <= nu; i++, k++) {
|
|
120
|
+
const p = surf(i / nu, j / nv);
|
|
121
|
+
pos[k * 3] = p[0];
|
|
122
|
+
pos[k * 3 + 1] = p[1];
|
|
123
|
+
pos[k * 3 + 2] = p[2];
|
|
124
|
+
ext = Math.max(
|
|
125
|
+
ext,
|
|
126
|
+
Math.abs(p[0] - pos[0]) + Math.abs(p[1] - pos[1]) + Math.abs(p[2] - pos[2]),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const P = (k: number): V3 => [pos[k * 3], pos[k * 3 + 1], pos[k * 3 + 2]];
|
|
131
|
+
const fn = new Float64Array(nu * nv * 3);
|
|
132
|
+
for (let j = 0; j < nv; j++) {
|
|
133
|
+
for (let i = 0; i < nu; i++) {
|
|
134
|
+
const a = j * W + i;
|
|
135
|
+
const A = P(a),
|
|
136
|
+
B = P(a + 1),
|
|
137
|
+
C = P(a + W + 1),
|
|
138
|
+
D = P(a + W);
|
|
139
|
+
/* the diagonals' cross product survives a quad that pinches to a
|
|
140
|
+
point at a pole */
|
|
141
|
+
let n = cross(sub(C, A), sub(D, B));
|
|
142
|
+
const mid: V3 = [
|
|
143
|
+
(A[0] + B[0] + C[0] + D[0]) / 4,
|
|
144
|
+
(A[1] + B[1] + C[1] + D[1]) / 4,
|
|
145
|
+
(A[2] + B[2] + C[2] + D[2]) / 4,
|
|
146
|
+
];
|
|
147
|
+
const ins = typeof inside === "function" ? inside((i + 0.5) / nu, (j + 0.5) / nv) : inside;
|
|
148
|
+
if (dot(n, sub(mid, ins)) < 0) n = [-n[0], -n[1], -n[2]];
|
|
149
|
+
n = norm(n);
|
|
150
|
+
const f = (j * nu + i) * 3;
|
|
151
|
+
fn[f] = n[0];
|
|
152
|
+
fn[f + 1] = n[1];
|
|
153
|
+
fn[f + 2] = n[2];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const eps = 1e-6 + ext * 1e-5;
|
|
157
|
+
const same = (p: number, q: number) =>
|
|
158
|
+
Math.abs(pos[p * 3] - pos[q * 3]) +
|
|
159
|
+
Math.abs(pos[p * 3 + 1] - pos[q * 3 + 1]) +
|
|
160
|
+
Math.abs(pos[p * 3 + 2] - pos[q * 3 + 2]) <
|
|
161
|
+
eps;
|
|
162
|
+
let closed = true;
|
|
163
|
+
for (let j = 0; j <= nv && closed; j++) closed = same(j * W, j * W + nu);
|
|
164
|
+
let vn: Float64Array | null = null;
|
|
165
|
+
if (smooth) {
|
|
166
|
+
const acc = new Float64Array(N * 3);
|
|
167
|
+
for (let j = 0; j < nv; j++) {
|
|
168
|
+
for (let i = 0; i < nu; i++) {
|
|
169
|
+
const f = (j * nu + i) * 3;
|
|
170
|
+
const a = j * W + i;
|
|
171
|
+
for (const k of [a, a + 1, a + W + 1, a + W]) {
|
|
172
|
+
acc[k * 3] += fn[f];
|
|
173
|
+
acc[k * 3 + 1] += fn[f + 1];
|
|
174
|
+
acc[k * 3 + 2] += fn[f + 2];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const join = (ks: number[]) => {
|
|
179
|
+
let x = 0,
|
|
180
|
+
y = 0,
|
|
181
|
+
z = 0;
|
|
182
|
+
for (const k of ks) {
|
|
183
|
+
x += acc[k * 3];
|
|
184
|
+
y += acc[k * 3 + 1];
|
|
185
|
+
z += acc[k * 3 + 2];
|
|
186
|
+
}
|
|
187
|
+
for (const k of ks) {
|
|
188
|
+
acc[k * 3] = x;
|
|
189
|
+
acc[k * 3 + 1] = y;
|
|
190
|
+
acc[k * 3 + 2] = z;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
if (closed) for (let j = 0; j <= nv; j++) join([j * W, j * W + nu]);
|
|
194
|
+
for (const j of [0, nv]) {
|
|
195
|
+
let pole = true;
|
|
196
|
+
for (let i = 1; i <= nu && pole; i++) pole = same(j * W, j * W + i);
|
|
197
|
+
if (pole) join(Array.from({ length: W }, (_, i) => j * W + i));
|
|
198
|
+
}
|
|
199
|
+
for (let k = 0; k < N; k++) {
|
|
200
|
+
const x = acc[k * 3],
|
|
201
|
+
y = acc[k * 3 + 1],
|
|
202
|
+
z = acc[k * 3 + 2];
|
|
203
|
+
const l = Math.hypot(x, y, z) || 1;
|
|
204
|
+
acc[k * 3] = x / l;
|
|
205
|
+
acc[k * 3 + 1] = y / l;
|
|
206
|
+
acc[k * 3 + 2] = z / l;
|
|
207
|
+
}
|
|
208
|
+
vn = acc;
|
|
209
|
+
}
|
|
210
|
+
return { nu, nv, closed, pos, fn, vn };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* A tube along a curve: `path(u)` its centre line, `up` the direction its
|
|
215
|
+
* cross-section is squared to (a band's depth axis, a rim's face normal),
|
|
216
|
+
* `prof(u, φ)` the cross-section's offset at angle φ — along the up
|
|
217
|
+
* direction and across it. Rows of the grid run along the tube.
|
|
218
|
+
*/
|
|
219
|
+
export function makeTube(
|
|
220
|
+
path: (u: number) => V3,
|
|
221
|
+
up: V3,
|
|
222
|
+
prof: (u: number, ph: number) => [number, number],
|
|
223
|
+
nu: number,
|
|
224
|
+
nv: number,
|
|
225
|
+
closed = false,
|
|
226
|
+
): Mesh {
|
|
227
|
+
const e = 1e-3;
|
|
228
|
+
const at = (u: number) => path(closed ? ((u % 1) + 1) % 1 : Math.max(0, Math.min(1, u)));
|
|
229
|
+
return makeMesh(
|
|
230
|
+
(u, v) => {
|
|
231
|
+
const c = path(u);
|
|
232
|
+
const T = norm(sub(at(u + e), at(u - e)));
|
|
233
|
+
const k = dot(up, T);
|
|
234
|
+
const Nn = norm([up[0] - T[0] * k, up[1] - T[1] * k, up[2] - T[2] * k]);
|
|
235
|
+
const Bn = cross(T, Nn);
|
|
236
|
+
const [a, b] = prof(u, v * TAU);
|
|
237
|
+
return [
|
|
238
|
+
c[0] + Nn[0] * a + Bn[0] * b,
|
|
239
|
+
c[1] + Nn[1] * a + Bn[1] * b,
|
|
240
|
+
c[2] + Nn[2] * a + Bn[2] * b,
|
|
241
|
+
];
|
|
242
|
+
},
|
|
243
|
+
nu,
|
|
244
|
+
nv,
|
|
245
|
+
(u) => path(u),
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** A cross-section: an ellipse (`p` = 1) squared toward a rounded box as
|
|
250
|
+
`p` falls, `a` along the up direction and `b` across it. */
|
|
251
|
+
export const section =
|
|
252
|
+
(a: number, b: number, p = 1) =>
|
|
253
|
+
(_u: number, ph: number): [number, number] => {
|
|
254
|
+
const c = Math.cos(ph),
|
|
255
|
+
s = Math.sin(ph);
|
|
256
|
+
return [
|
|
257
|
+
a * Math.sign(c) * Math.pow(Math.abs(c), p),
|
|
258
|
+
b * Math.sign(s) * Math.pow(Math.abs(s), p),
|
|
259
|
+
];
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/** A mesh as this frame sees it: every corner on screen with its depth
|
|
263
|
+
and its normal in view space, and every cell's normal and depth. */
|
|
264
|
+
export interface Proj {
|
|
265
|
+
nu: number;
|
|
266
|
+
nv: number;
|
|
267
|
+
closed: boolean;
|
|
268
|
+
x: Float64Array;
|
|
269
|
+
y: Float64Array;
|
|
270
|
+
d: Float64Array;
|
|
271
|
+
/** the corners' normals, 3 a corner; null for a faceted mesh */
|
|
272
|
+
n: Float64Array | null;
|
|
273
|
+
/** the cells' normals, 3 a cell, and their mean depths */
|
|
274
|
+
f: Float64Array;
|
|
275
|
+
cd: Float64Array;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function projectMesh(view: View, mesh: Mesh): Proj {
|
|
279
|
+
const { nu, nv, pos, fn, vn } = mesh;
|
|
280
|
+
const W = nu + 1,
|
|
281
|
+
N = W * (nv + 1),
|
|
282
|
+
C = nu * nv;
|
|
283
|
+
const [m0, m1, m2, m3, m4, m5, m6, m7, m8] = view.m;
|
|
284
|
+
const [ox, oy, oz] = view.o;
|
|
285
|
+
const x = new Float64Array(N),
|
|
286
|
+
y = new Float64Array(N),
|
|
287
|
+
d = new Float64Array(N);
|
|
288
|
+
for (let k = 0; k < N; k++) {
|
|
289
|
+
const X = pos[k * 3] - ox,
|
|
290
|
+
Y = pos[k * 3 + 1] - oy,
|
|
291
|
+
Z = pos[k * 3 + 2] - oz;
|
|
292
|
+
x[k] = m0 * X + m1 * Y + m2 * Z;
|
|
293
|
+
y[k] = m3 * X + m4 * Y + m5 * Z;
|
|
294
|
+
d[k] = m6 * X + m7 * Y + m8 * Z;
|
|
295
|
+
}
|
|
296
|
+
const turn = (src: Float64Array, dst: Float64Array, count: number) => {
|
|
297
|
+
for (let k = 0; k < count; k++) {
|
|
298
|
+
const a = src[k * 3],
|
|
299
|
+
b = src[k * 3 + 1],
|
|
300
|
+
c = src[k * 3 + 2];
|
|
301
|
+
const X = m0 * a + m1 * b + m2 * c,
|
|
302
|
+
Y = m3 * a + m4 * b + m5 * c,
|
|
303
|
+
Z = m6 * a + m7 * b + m8 * c;
|
|
304
|
+
const l = Math.hypot(X, Y, Z) || 1;
|
|
305
|
+
dst[k * 3] = X / l;
|
|
306
|
+
dst[k * 3 + 1] = Y / l;
|
|
307
|
+
dst[k * 3 + 2] = Z / l;
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
let n: Float64Array | null = null;
|
|
311
|
+
if (vn) turn(vn, (n = new Float64Array(N * 3)), N);
|
|
312
|
+
const f = new Float64Array(C * 3),
|
|
313
|
+
cd = new Float64Array(C);
|
|
314
|
+
turn(fn, f, C);
|
|
315
|
+
for (let j = 0, c = 0; j < nv; j++) {
|
|
316
|
+
for (let i = 0; i < nu; i++, c++) {
|
|
317
|
+
const a = j * W + i;
|
|
318
|
+
cd[c] = (d[a] + d[a + 1] + d[a + W] + d[a + W + 1]) / 4;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { nu, nv, closed: mesh.closed, x, y, d, n, f, cd };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** The screen bounds of some projected meshes. */
|
|
325
|
+
export function boundsOf(ps: Proj[]) {
|
|
326
|
+
let x0 = Infinity,
|
|
327
|
+
y0 = Infinity,
|
|
328
|
+
x1 = -Infinity,
|
|
329
|
+
y1 = -Infinity;
|
|
330
|
+
for (const p of ps) {
|
|
331
|
+
for (let k = 0; k < p.x.length; k++) {
|
|
332
|
+
if (p.x[k] < x0) x0 = p.x[k];
|
|
333
|
+
if (p.x[k] > x1) x1 = p.x[k];
|
|
334
|
+
if (p.y[k] < y0) y0 = p.y[k];
|
|
335
|
+
if (p.y[k] > y1) y1 = p.y[k];
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return { x0, y0, x1, y1, cx: (x0 + x1) / 2, cy: (y0 + y1) / 2, w: x1 - x0, h: y1 - y0 };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* ── painting ──────────────────────────────────────────────────────── */
|
|
342
|
+
|
|
343
|
+
export interface Layer {
|
|
344
|
+
parts: Proj[];
|
|
345
|
+
shade: Shade;
|
|
346
|
+
/** keep the cells facing away (thin plates seen from behind) */
|
|
347
|
+
cull?: boolean;
|
|
348
|
+
/** a cell at a time, one colour each: cut facets */
|
|
349
|
+
flat?: boolean;
|
|
350
|
+
/** a nudge toward the viewer in the depth sort: a part set into another
|
|
351
|
+
(a ring round a plate) wins where they meet */
|
|
352
|
+
bias?: number;
|
|
353
|
+
/** keep only the cells whose depth passes: one side of a split */
|
|
354
|
+
keep?: (d: number) => boolean;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
interface Piece {
|
|
358
|
+
d: number;
|
|
359
|
+
l: Layer;
|
|
360
|
+
p: Proj;
|
|
361
|
+
j: number;
|
|
362
|
+
/** the strip's columns (corner indices along its row) and its cells; one
|
|
363
|
+
cell alone for a flat piece */
|
|
364
|
+
cols: number[];
|
|
365
|
+
cells: number[];
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* is the run of column midpoints s … e one way along its chord */
|
|
369
|
+
function monotone(mx: number[], my: number[], s: number, e: number): boolean {
|
|
370
|
+
const ax = mx[e] - mx[s],
|
|
371
|
+
ay = my[e] - my[s];
|
|
372
|
+
let prev = -Infinity;
|
|
373
|
+
for (let k = s; k <= e; k++) {
|
|
374
|
+
const t = (mx[k] - mx[s]) * ax + (my[k] - my[s]) * ay;
|
|
375
|
+
if (t < prev - 1e-9) return false;
|
|
376
|
+
prev = t;
|
|
377
|
+
}
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const clampByte = (v: number) => (v < 0 ? 0 : v > 255 ? 255 : Math.round(v));
|
|
382
|
+
export const css = (c: RGB) => `rgb(${clampByte(c[0])},${clampByte(c[1])},${clampByte(c[2])})`;
|
|
383
|
+
|
|
384
|
+
/** Paint several surfaces as one, back to front, the parts facing away
|
|
385
|
+
dropped. `px` is the view's scale. Returns the corners painted, for an
|
|
386
|
+
outline. */
|
|
387
|
+
export function paintLayers(ctx: CanvasRenderingContext2D, layers: Layer[], px: number): P2[] {
|
|
388
|
+
const pieces: Piece[] = [];
|
|
389
|
+
const corners: P2[] = [];
|
|
390
|
+
for (const l of layers) {
|
|
391
|
+
const bias = l.bias ?? 0,
|
|
392
|
+
keep = l.keep,
|
|
393
|
+
cull = l.cull !== false;
|
|
394
|
+
for (const P of l.parts) {
|
|
395
|
+
const { nu, nv, f, cd, x, y } = P,
|
|
396
|
+
W = nu + 1;
|
|
397
|
+
const vis = (c: number) => (!cull || f[c * 3 + 2] > -0.04) && (!keep || keep(cd[c]));
|
|
398
|
+
for (let j = 0; j < nv; j++) {
|
|
399
|
+
if (l.flat) {
|
|
400
|
+
for (let i = 0; i < nu; i++) {
|
|
401
|
+
const c = j * nu + i;
|
|
402
|
+
if (!vis(c)) continue;
|
|
403
|
+
pieces.push({ d: cd[c] + bias, l, p: P, j, cols: [i, i + 1], cells: [c] });
|
|
404
|
+
corners.push(
|
|
405
|
+
[x[j * W + i], y[j * W + i]],
|
|
406
|
+
[x[(j + 1) * W + i + 1], y[(j + 1) * W + i + 1]],
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
/* the row's visible runs, joined across the seam of a closed grid */
|
|
412
|
+
const runs: number[][] = [];
|
|
413
|
+
let cur: number[] | null = null;
|
|
414
|
+
for (let i = 0; i < nu; i++) {
|
|
415
|
+
if (vis(j * nu + i)) {
|
|
416
|
+
if (!cur) runs.push((cur = []));
|
|
417
|
+
cur.push(i);
|
|
418
|
+
} else cur = null;
|
|
419
|
+
}
|
|
420
|
+
if (
|
|
421
|
+
P.closed &&
|
|
422
|
+
runs.length > 1 &&
|
|
423
|
+
runs[0][0] === 0 &&
|
|
424
|
+
runs[runs.length - 1][runs[runs.length - 1].length - 1] === nu - 1
|
|
425
|
+
) {
|
|
426
|
+
runs[0] = runs.pop()!.concat(runs[0]);
|
|
427
|
+
}
|
|
428
|
+
for (const run of runs) {
|
|
429
|
+
const cols = run.concat(run[run.length - 1] + 1);
|
|
430
|
+
const mx: number[] = [],
|
|
431
|
+
my: number[] = [];
|
|
432
|
+
for (let k = 0; k < cols.length; k++) {
|
|
433
|
+
const t = j * W + cols[k],
|
|
434
|
+
b = t + W;
|
|
435
|
+
mx.push((x[t] + x[b]) / 2);
|
|
436
|
+
my.push((y[t] + y[b]) / 2);
|
|
437
|
+
/* the outline needs only the silhouette: a run's ends, and the
|
|
438
|
+
grid's first and last rows */
|
|
439
|
+
if (k === 0 || k === cols.length - 1) corners.push([x[t], y[t]], [x[b], y[b]]);
|
|
440
|
+
else if (j === 0) corners.push([x[t], y[t]]);
|
|
441
|
+
else if (j === nv - 1) corners.push([x[b], y[b]]);
|
|
442
|
+
}
|
|
443
|
+
/* split where the row turns back on itself on screen, so each
|
|
444
|
+
strip can take one linear gradient */
|
|
445
|
+
let s = 0;
|
|
446
|
+
while (s < cols.length - 1) {
|
|
447
|
+
let e = s + 1;
|
|
448
|
+
while (e + 1 < cols.length && monotone(mx, my, s, e + 1)) e++;
|
|
449
|
+
let dd = 0;
|
|
450
|
+
for (let k = s; k < e; k++) dd += cd[j * nu + run[k]];
|
|
451
|
+
pieces.push({
|
|
452
|
+
d: dd / (e - s) + bias,
|
|
453
|
+
l,
|
|
454
|
+
p: P,
|
|
455
|
+
j,
|
|
456
|
+
cols: cols.slice(s, e + 1),
|
|
457
|
+
cells: run.slice(s, e),
|
|
458
|
+
});
|
|
459
|
+
s = e;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
pieces.sort((a, b) => a.d - b.d);
|
|
466
|
+
const grow = 0.5 / px;
|
|
467
|
+
for (const pc of pieces) {
|
|
468
|
+
const { p: P, l, j, cols, cells } = pc;
|
|
469
|
+
const { x, y, n, f } = P,
|
|
470
|
+
W = P.nu + 1;
|
|
471
|
+
const nc = cols.length;
|
|
472
|
+
/* the strip's outline, pushed out a little all round so neighbours
|
|
473
|
+
overlap and no seam of background shows */
|
|
474
|
+
const tx: number[] = [],
|
|
475
|
+
ty: number[] = [],
|
|
476
|
+
bx: number[] = [],
|
|
477
|
+
by: number[] = [];
|
|
478
|
+
const t0 = j * W + cols[0],
|
|
479
|
+
tn = j * W + cols[nc - 1];
|
|
480
|
+
let ax = (x[tn] + x[tn + W]) / 2 - (x[t0] + x[t0 + W]) / 2,
|
|
481
|
+
ay = (y[tn] + y[tn + W]) / 2 - (y[t0] + y[t0 + W]) / 2;
|
|
482
|
+
const al = Math.hypot(ax, ay);
|
|
483
|
+
const ux = al > 1e-9 ? ax / al : 0,
|
|
484
|
+
uy = al > 1e-9 ? ay / al : 0;
|
|
485
|
+
for (let k = 0; k < nc; k++) {
|
|
486
|
+
const t = j * W + cols[k],
|
|
487
|
+
b = t + W;
|
|
488
|
+
let dx = x[t] - x[b],
|
|
489
|
+
dy = y[t] - y[b];
|
|
490
|
+
const dl = Math.hypot(dx, dy);
|
|
491
|
+
if (dl > 1e-9) ((dx /= dl), (dy /= dl));
|
|
492
|
+
else dx = dy = 0;
|
|
493
|
+
const e = k === 0 ? -grow : k === nc - 1 ? grow : 0;
|
|
494
|
+
tx.push(x[t] + dx * grow + ux * e);
|
|
495
|
+
ty.push(y[t] + dy * grow + uy * e);
|
|
496
|
+
bx.push(x[b] - dx * grow + ux * e);
|
|
497
|
+
by.push(y[b] - dy * grow + uy * e);
|
|
498
|
+
}
|
|
499
|
+
/* the colour at each column: from the corners' normals, or the cells'
|
|
500
|
+
beside it on a faceted mesh; stops only where the colour has moved */
|
|
501
|
+
const colour = (k: number): RGB => {
|
|
502
|
+
if (n && !l.flat) {
|
|
503
|
+
const t = (j * W + cols[k]) * 3,
|
|
504
|
+
b = t + W * 3;
|
|
505
|
+
return l.shade(norm([n[t] + n[b], n[t + 1] + n[b + 1], n[t + 2] + n[b + 2]]));
|
|
506
|
+
}
|
|
507
|
+
const c0 = cells[Math.max(0, Math.min(cells.length - 1, k - 1))] * 3,
|
|
508
|
+
c1 = cells[Math.min(cells.length - 1, k)] * 3;
|
|
509
|
+
return l.shade(
|
|
510
|
+
l.flat
|
|
511
|
+
? [f[c1], f[c1 + 1], f[c1 + 2]]
|
|
512
|
+
: norm([f[c0] + f[c1], f[c0 + 1] + f[c1 + 1], f[c0 + 2] + f[c1 + 2]]),
|
|
513
|
+
);
|
|
514
|
+
};
|
|
515
|
+
let style: string | CanvasGradient;
|
|
516
|
+
if (l.flat || al < 1e-6) style = css(colour(0));
|
|
517
|
+
else {
|
|
518
|
+
const g = ctx.createLinearGradient(
|
|
519
|
+
(x[t0] + x[t0 + W]) / 2,
|
|
520
|
+
(y[t0] + y[t0 + W]) / 2,
|
|
521
|
+
(x[tn] + x[tn + W]) / 2,
|
|
522
|
+
(y[tn] + y[tn + W]) / 2,
|
|
523
|
+
);
|
|
524
|
+
let last: RGB | null = null;
|
|
525
|
+
const m0x = (x[t0] + x[t0 + W]) / 2,
|
|
526
|
+
m0y = (y[t0] + y[t0 + W]) / 2;
|
|
527
|
+
for (let k = 0; k < nc; k++) {
|
|
528
|
+
const c = colour(k);
|
|
529
|
+
if (
|
|
530
|
+
last &&
|
|
531
|
+
k < nc - 1 &&
|
|
532
|
+
Math.abs(c[0] - last[0]) + Math.abs(c[1] - last[1]) + Math.abs(c[2] - last[2]) < 5
|
|
533
|
+
)
|
|
534
|
+
continue;
|
|
535
|
+
const t = j * W + cols[k];
|
|
536
|
+
const off =
|
|
537
|
+
(((x[t] + x[t + W]) / 2 - m0x) * ax + ((y[t] + y[t + W]) / 2 - m0y) * ay) / (al * al);
|
|
538
|
+
g.addColorStop(off < 0 ? 0 : off > 1 ? 1 : off, css(c));
|
|
539
|
+
last = c;
|
|
540
|
+
}
|
|
541
|
+
style = g;
|
|
542
|
+
}
|
|
543
|
+
ctx.fillStyle = style;
|
|
544
|
+
ctx.beginPath();
|
|
545
|
+
ctx.moveTo(tx[0], ty[0]);
|
|
546
|
+
for (let k = 1; k < nc; k++) ctx.lineTo(tx[k], ty[k]);
|
|
547
|
+
for (let k = nc - 1; k >= 0; k--) ctx.lineTo(bx[k], by[k]);
|
|
548
|
+
ctx.closePath();
|
|
549
|
+
ctx.fill();
|
|
550
|
+
}
|
|
551
|
+
return corners;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export function hull(pts: P2[]): P2[] {
|
|
555
|
+
const p = pts.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
556
|
+
if (p.length < 3) return p;
|
|
557
|
+
const cr = (o: P2, a: P2, b: P2) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
|
558
|
+
const lo: P2[] = [],
|
|
559
|
+
up: P2[] = [];
|
|
560
|
+
for (const q of p) {
|
|
561
|
+
while (lo.length >= 2 && cr(lo[lo.length - 2], lo[lo.length - 1], q) <= 0) lo.pop();
|
|
562
|
+
lo.push(q);
|
|
563
|
+
}
|
|
564
|
+
for (let i = p.length - 1; i >= 0; i--) {
|
|
565
|
+
const q = p[i];
|
|
566
|
+
while (up.length >= 2 && cr(up[up.length - 2], up[up.length - 1], q) <= 0) up.pop();
|
|
567
|
+
up.push(q);
|
|
568
|
+
}
|
|
569
|
+
lo.pop();
|
|
570
|
+
up.pop();
|
|
571
|
+
return lo.concat(up);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* ── materials: a view and a base colour to a shade ────────────────── */
|
|
575
|
+
|
|
576
|
+
const clamp01 = (x: number) => (x < 0 ? 0 : x > 1 ? 1 : x);
|
|
577
|
+
export const mix = (a: RGB, b: RGB, t: number): RGB => [
|
|
578
|
+
a[0] + (b[0] - a[0]) * t,
|
|
579
|
+
a[1] + (b[1] - a[1]) * t,
|
|
580
|
+
a[2] + (b[2] - a[2]) * t,
|
|
581
|
+
];
|
|
582
|
+
export const scale = (a: RGB, k: number): RGB => [a[0] * k, a[1] * k, a[2] * k];
|
|
583
|
+
const WHITE: RGB = [255, 255, 255];
|
|
584
|
+
|
|
585
|
+
/** Felt and knit: matte, the light wrapping softly round, a faint sheen
|
|
586
|
+
where the fibres lie side-on; a dark felt keeps a floor of light so it
|
|
587
|
+
reads charcoal on its lit side. `k` darkens it (a fold, an underside). */
|
|
588
|
+
export function felt(view: View, base: RGB, k = 1): Shade {
|
|
589
|
+
const L = view.L;
|
|
590
|
+
return (n) => {
|
|
591
|
+
const nl = dot(n, L);
|
|
592
|
+
const dif = Math.pow(clamp01((nl + 0.4) / 1.4), 1.35);
|
|
593
|
+
const graze = Math.pow(1 - clamp01(n[2]), 2);
|
|
594
|
+
const lit = (0.22 + 0.95 * dif + 0.22 * graze * clamp01(nl + 0.6)) * k;
|
|
595
|
+
const floor = 26 * dif * k;
|
|
596
|
+
return [base[0] * lit + floor, base[1] * lit + floor, base[2] * lit + floor * 1.05];
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** Velvet: deep in its folds, its colour lifting toward the silhouette. */
|
|
601
|
+
export function velvet(view: View, base: RGB): Shade {
|
|
602
|
+
const L = view.L,
|
|
603
|
+
sheen = mix(base, WHITE, 0.3);
|
|
604
|
+
return (n) => {
|
|
605
|
+
const nl = dot(n, L);
|
|
606
|
+
const dif = clamp01((nl + 0.3) / 1.3);
|
|
607
|
+
const graze = Math.pow(1 - clamp01(n[2]), 1.6);
|
|
608
|
+
return mix(scale(base, 0.2 + 0.7 * dif), sheen, 0.6 * graze * (0.4 + 0.6 * dif));
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Paper: Lambert with a soft wide sheen. */
|
|
613
|
+
export function paper(view: View, base: RGB): Shade {
|
|
614
|
+
const L = view.L,
|
|
615
|
+
H = view.H;
|
|
616
|
+
return (n) => {
|
|
617
|
+
const dif = clamp01((dot(n, L) + 0.2) / 1.2);
|
|
618
|
+
const spec = Math.pow(clamp01(dot(n, H)), 22) * 0.3;
|
|
619
|
+
return mix(scale(base, 0.36 + 0.7 * dif), WHITE, spec);
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Soft plastic and leather: diffuse, a broad sheen and a tighter hot
|
|
624
|
+
spot, a thin rim of light at the silhouette. */
|
|
625
|
+
export function plastic(view: View, base: RGB, gloss = 1): Shade {
|
|
626
|
+
const L = view.L,
|
|
627
|
+
H = view.H;
|
|
628
|
+
return (n) => {
|
|
629
|
+
const dif = clamp01((dot(n, L) + 0.25) / 1.25);
|
|
630
|
+
const nh = clamp01(dot(n, H));
|
|
631
|
+
const spec = (Math.pow(nh, 16) * 0.2 + Math.pow(nh, 80) * 0.6) * gloss;
|
|
632
|
+
const rim = Math.pow(1 - clamp01(n[2]), 3) * 0.14 * gloss;
|
|
633
|
+
return mix(scale(base, 0.28 + 0.78 * dif), WHITE, clamp01(spec + rim));
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** Satin: a silk ribbon's light — a wide soft sheen that runs along the
|
|
638
|
+
folds and a deep, saturated shade between them. */
|
|
639
|
+
export function satin(view: View, base: RGB): Shade {
|
|
640
|
+
const L = view.L,
|
|
641
|
+
H = view.H,
|
|
642
|
+
deep = scale(base, 0.42),
|
|
643
|
+
sheen = mix(base, WHITE, 0.55);
|
|
644
|
+
return (n) => {
|
|
645
|
+
const dif = clamp01((dot(n, L) + 0.35) / 1.35);
|
|
646
|
+
const nh = clamp01(dot(n, H));
|
|
647
|
+
const s = Math.pow(nh, 6) * 0.5 + Math.pow(nh, 40) * 0.35;
|
|
648
|
+
return mix(mix(deep, base, dif), sheen, clamp01(s));
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** Metal: the colour of what it reflects — a bright sky above, a dark
|
|
653
|
+
floor below, a bright horizon between — tinted, with a hot glint. */
|
|
654
|
+
export function metal(view: View, tint: RGB, dark: RGB, glint: RGB): Shade {
|
|
655
|
+
const H = view.H,
|
|
656
|
+
L = view.L;
|
|
657
|
+
return (n) => {
|
|
658
|
+
/* the reflection of the view about the normal; up is −y */
|
|
659
|
+
const rz = 2 * n[2] * n[2] - 1,
|
|
660
|
+
ry = 2 * n[2] * n[1];
|
|
661
|
+
const up = -ry;
|
|
662
|
+
const sky = clamp01(0.45 + up * 0.85 + rz * 0.1);
|
|
663
|
+
const horizon = Math.exp(-Math.pow((up + 0.12) / 0.16, 2)) * 0.55;
|
|
664
|
+
const nl = clamp01(dot(n, L));
|
|
665
|
+
const c = mix(dark, tint, clamp01(0.1 + sky * 0.72 + nl * 0.3));
|
|
666
|
+
const spec = Math.pow(clamp01(dot(n, H)), 70) + horizon * 0.4;
|
|
667
|
+
return mix(c, glint, clamp01(spec));
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
export const gold = (view: View) => metal(view, [255, 200, 88], [86, 50, 8], [255, 247, 220]);
|
|
671
|
+
export const silver = (view: View) => metal(view, [232, 236, 242], [58, 62, 72], [255, 255, 255]);
|
|
672
|
+
|
|
673
|
+
/** A pearl: soft white with a pink-blue play and a small bright glint. */
|
|
674
|
+
export function pearl(view: View): Shade {
|
|
675
|
+
const L = view.L,
|
|
676
|
+
H = view.H;
|
|
677
|
+
return (n) => {
|
|
678
|
+
const dif = clamp01((dot(n, L) + 0.4) / 1.4);
|
|
679
|
+
const base = mix([238, 228, 228], [212, 222, 242], 1 - clamp01(n[2]));
|
|
680
|
+
return mix(scale(base, 0.62 + 0.42 * dif), WHITE, Math.pow(clamp01(dot(n, H)), 40) * 0.8);
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** A cut gem's facet: its colour deepening toward the edges, the facets
|
|
685
|
+
turned to the light bright, a white fire on the one that catches it. */
|
|
686
|
+
export function gem(view: View, base: RGB): Shade {
|
|
687
|
+
const L = view.L,
|
|
688
|
+
H = view.H,
|
|
689
|
+
deep = scale(base, 0.22),
|
|
690
|
+
clear = mix(base, WHITE, 0.3);
|
|
691
|
+
return (n) => {
|
|
692
|
+
const c = mix(deep, clear, clamp01(clamp01(n[2]) * 0.75 + dot(n, L) * 0.4));
|
|
693
|
+
return mix(c, WHITE, Math.pow(clamp01(dot(n, H)), 24) * 0.95);
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/* ── single fills: spheres and cut stones ──────────────────────────── */
|
|
698
|
+
|
|
699
|
+
export type SpriteKind = "gold" | "silver" | "pearl";
|
|
700
|
+
const SPRITE: Record<SpriteKind, [number, string][]> = {
|
|
701
|
+
gold: [
|
|
702
|
+
[0, "#fffbe8"],
|
|
703
|
+
[0.08, "#ffe7a0"],
|
|
704
|
+
[0.3, "#eeb546"],
|
|
705
|
+
[0.62, "#ad7214"],
|
|
706
|
+
[0.87, "#5a3806"],
|
|
707
|
+
[1, "#8c5c18"],
|
|
708
|
+
],
|
|
709
|
+
silver: [
|
|
710
|
+
[0, "#ffffff"],
|
|
711
|
+
[0.08, "#f3f5f9"],
|
|
712
|
+
[0.32, "#c6ccd6"],
|
|
713
|
+
[0.64, "#7a808c"],
|
|
714
|
+
[0.88, "#3d4149"],
|
|
715
|
+
[1, "#6b717c"],
|
|
716
|
+
],
|
|
717
|
+
pearl: [
|
|
718
|
+
[0, "#ffffff"],
|
|
719
|
+
[0.1, "#fdf9f7"],
|
|
720
|
+
[0.45, "#ebe4e6"],
|
|
721
|
+
[0.8, "#c6bccb"],
|
|
722
|
+
[1, "#a89db5"],
|
|
723
|
+
],
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
/** A small sphere as one radial fill: its hot spot where the normal meets
|
|
727
|
+
the half vector, falling to a dark edge with a little reflected light. */
|
|
728
|
+
export function sphereSprite(
|
|
729
|
+
ctx: CanvasRenderingContext2D,
|
|
730
|
+
view: View,
|
|
731
|
+
c: V3,
|
|
732
|
+
rad: number,
|
|
733
|
+
kind: SpriteKind | RGB,
|
|
734
|
+
) {
|
|
735
|
+
const s = view.toScreen(c);
|
|
736
|
+
const H = view.H;
|
|
737
|
+
const g = ctx.createRadialGradient(
|
|
738
|
+
s[0] + H[0] * rad * 0.85,
|
|
739
|
+
s[1] + H[1] * rad * 0.85,
|
|
740
|
+
0,
|
|
741
|
+
s[0],
|
|
742
|
+
s[1],
|
|
743
|
+
rad,
|
|
744
|
+
);
|
|
745
|
+
if (typeof kind === "string") for (const [t, col] of SPRITE[kind]) g.addColorStop(t, col);
|
|
746
|
+
else {
|
|
747
|
+
g.addColorStop(0, css(mix(kind, WHITE, 0.8)));
|
|
748
|
+
g.addColorStop(0.1, css(mix(kind, WHITE, 0.3)));
|
|
749
|
+
g.addColorStop(0.35, css(kind));
|
|
750
|
+
g.addColorStop(0.85, css(scale(kind, 0.5)));
|
|
751
|
+
g.addColorStop(1, css(scale(kind, 0.62)));
|
|
752
|
+
}
|
|
753
|
+
ctx.fillStyle = g;
|
|
754
|
+
ctx.beginPath();
|
|
755
|
+
ctx.arc(s[0], s[1], rad, 0, TAU);
|
|
756
|
+
ctx.fill();
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* A cut stone set in a surface: `c` its centre, `N` the surface's outward
|
|
761
|
+
* normal, `T` a tangent (object space, unit). An eight-sided girdle, a
|
|
762
|
+
* table on top and the crown facets between, each lit by its own normal.
|
|
763
|
+
*/
|
|
764
|
+
export function cutGem(
|
|
765
|
+
ctx: CanvasRenderingContext2D,
|
|
766
|
+
view: View,
|
|
767
|
+
c: V3,
|
|
768
|
+
N: V3,
|
|
769
|
+
T: V3,
|
|
770
|
+
rad: number,
|
|
771
|
+
col: RGB,
|
|
772
|
+
) {
|
|
773
|
+
const B = cross(N, T);
|
|
774
|
+
const at = (r: number, a: number, h: number): V3 => {
|
|
775
|
+
const x = r * Math.cos(a),
|
|
776
|
+
y = r * Math.sin(a);
|
|
777
|
+
return [
|
|
778
|
+
c[0] + x * T[0] + y * B[0] + h * N[0],
|
|
779
|
+
c[1] + x * T[1] + y * B[1] + h * N[1],
|
|
780
|
+
c[2] + x * T[2] + y * B[2] + h * N[2],
|
|
781
|
+
];
|
|
782
|
+
};
|
|
783
|
+
const K = 8,
|
|
784
|
+
tr = 0.55,
|
|
785
|
+
th = 0.36 * rad;
|
|
786
|
+
const girdle: V3[] = [],
|
|
787
|
+
table: V3[] = [];
|
|
788
|
+
for (let k = 0; k < K; k++) {
|
|
789
|
+
const a = ((k + 0.5) / K) * TAU;
|
|
790
|
+
girdle.push(at(rad, a, 0));
|
|
791
|
+
table.push(at(rad * tr, a, th));
|
|
792
|
+
}
|
|
793
|
+
const shade = gem(view, col);
|
|
794
|
+
const poly = (pts: V3[], n: V3) => {
|
|
795
|
+
const nv = view.dir(n);
|
|
796
|
+
if (nv[2] <= 0.02) return;
|
|
797
|
+
ctx.fillStyle = css(shade(nv));
|
|
798
|
+
ctx.beginPath();
|
|
799
|
+
pts.forEach((p, i) => {
|
|
800
|
+
const s = view.toScreen(p);
|
|
801
|
+
if (i) ctx.lineTo(s[0], s[1]);
|
|
802
|
+
else ctx.moveTo(s[0], s[1]);
|
|
803
|
+
});
|
|
804
|
+
ctx.closePath();
|
|
805
|
+
ctx.fill();
|
|
806
|
+
ctx.strokeStyle = ctx.fillStyle;
|
|
807
|
+
ctx.stroke();
|
|
808
|
+
};
|
|
809
|
+
ctx.lineWidth = Math.min(0.3, 1 / view.px);
|
|
810
|
+
ctx.lineJoin = "round";
|
|
811
|
+
for (let k = 0; k < K; k++) {
|
|
812
|
+
const g0 = girdle[k],
|
|
813
|
+
g1 = girdle[(k + 1) % K],
|
|
814
|
+
t1 = table[(k + 1) % K],
|
|
815
|
+
t0 = table[k];
|
|
816
|
+
let n = cross(sub(t1, g0), sub(t0, g1));
|
|
817
|
+
if (dot(n, N) < 0) n = [-n[0], -n[1], -n[2]];
|
|
818
|
+
poly([g0, g1, t1, t0], norm(n));
|
|
819
|
+
}
|
|
820
|
+
poly(table, N);
|
|
821
|
+
/* the table's inner light: a lighter kite toward the light */
|
|
822
|
+
const nv = view.dir(N);
|
|
823
|
+
if (nv[2] > 0.1) {
|
|
824
|
+
const s0 = view.toScreen(at(0, 0, th));
|
|
825
|
+
const L = view.L;
|
|
826
|
+
ctx.fillStyle = "rgba(255,255,255,0.3)";
|
|
827
|
+
ctx.beginPath();
|
|
828
|
+
const s1 = view.toScreen(at(rad * tr * 0.8, Math.atan2(-L[1], -L[0]) + 2.2, th));
|
|
829
|
+
const s2 = view.toScreen(at(rad * tr * 0.9, Math.atan2(-L[1], -L[0]) + Math.PI, th));
|
|
830
|
+
const s3 = view.toScreen(at(rad * tr * 0.8, Math.atan2(-L[1], -L[0]) + Math.PI + 0.94, th));
|
|
831
|
+
ctx.moveTo(s0[0], s0[1]);
|
|
832
|
+
ctx.lineTo(s1[0], s1[1]);
|
|
833
|
+
ctx.lineTo(s2[0], s2[1]);
|
|
834
|
+
ctx.lineTo(s3[0], s3[1]);
|
|
835
|
+
ctx.closePath();
|
|
836
|
+
ctx.fill();
|
|
837
|
+
}
|
|
838
|
+
}
|