evomeme 0.1.9 → 0.1.10
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/dist/canvas-public/app.js +509 -5999
- package/dist/canvas-public/assets/presets/catalog.json +47 -0
- package/dist/canvas-public/assets/presets/tiles/A_01_CaoDi.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_02_ShaMo.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_03_TianKong.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_04_ShuiBo.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_05_HaiMian.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_06_MuDiBan.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_07_ShiZhuan.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_08_AnRongYan.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_09_QingTaiShi.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_10_ShenHai.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_11_HuiYanShi.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_12_HongTu.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_13_GengDi.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_14_HuoShanYan.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_15_NiTu.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_16_HuangSha.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_17_CongLin.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_18_ZiYanShi.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_19_MoGuTu.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_20_HuaDian.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_21_HaiLang.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_22_GanTu.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_23_QingTaiShiBan.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_24_XingKong.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_25_MoFaFuWen.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_26_ShiTuYeDi.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_27_LanZhuan.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_28_HongZhuan.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_29_YueQiu.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_30_JinShuGe.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_31_JinShuJingGai.png +0 -0
- package/dist/canvas-public/assets/presets/tiles/A_32_ShuZiChuang.png +0 -0
- package/dist/canvas-public/canvas/actions.js +143 -0
- package/dist/canvas-public/canvas/camera.js +28 -0
- package/dist/canvas-public/canvas/chrome.js +13 -0
- package/dist/canvas-public/canvas/data.js +123 -0
- package/dist/canvas-public/canvas/dependencies.js +19 -0
- package/dist/canvas-public/canvas/interactions.js +544 -0
- package/dist/canvas-public/canvas/panel-resize.js +5 -0
- package/dist/canvas-public/canvas/renderer.js +297 -0
- package/dist/canvas-public/canvas/selection.js +62 -0
- package/dist/canvas-public/canvas/serialization.js +118 -0
- package/dist/canvas-public/canvas/sync.js +202 -0
- package/dist/canvas-public/core/api.js +27 -0
- package/dist/canvas-public/core/feature-registry.js +29 -0
- package/dist/canvas-public/core/feature-state.js +228 -0
- package/dist/canvas-public/core/layout.js +133 -0
- package/dist/canvas-public/core/operations.js +38 -0
- package/dist/canvas-public/core/project.js +203 -0
- package/dist/canvas-public/core/state.js +27 -0
- package/dist/canvas-public/dialogs/context-menu.js +118 -0
- package/dist/canvas-public/elements/base.js +100 -0
- package/dist/canvas-public/elements/connections.js +105 -0
- package/dist/canvas-public/elements/drag.js +53 -0
- package/dist/canvas-public/elements/image.js +259 -0
- package/dist/canvas-public/elements/loading-recovery.js +5 -0
- package/dist/canvas-public/elements/loading.js +21 -0
- package/dist/canvas-public/elements/map.js +64 -0
- package/dist/canvas-public/elements/registry.js +58 -0
- package/dist/canvas-public/elements/replacement.js +78 -0
- package/dist/canvas-public/elements/resize.js +210 -0
- package/dist/canvas-public/elements/task-pipeline.js +234 -0
- package/dist/canvas-public/elements/types.js +21 -0
- package/dist/canvas-public/elements/ui.js +14 -0
- package/dist/canvas-public/elements/video.js +64 -0
- package/dist/canvas-public/features/assets/local-assets.js +72 -0
- package/dist/canvas-public/features/assets/official-assets.js +538 -0
- package/dist/canvas-public/features/editing/canvas-size.js +532 -0
- package/dist/canvas-public/features/editing/color-key.js +469 -0
- package/dist/canvas-public/features/editing/cutout.js +20 -0
- package/dist/canvas-public/features/editing/interactive-image-editing.js +981 -0
- package/dist/canvas-public/features/editing/layer-decomposition.js +44 -0
- package/dist/canvas-public/features/editing/manual-split.js +114 -0
- package/dist/canvas-public/features/editing/perfect-pixel.js +336 -0
- package/dist/canvas-public/features/editing/scroll-preview.js +129 -0
- package/dist/canvas-public/features/editing/scroll-seam-elimination.js +71 -0
- package/dist/canvas-public/features/editing/seam-elimination.js +46 -0
- package/dist/canvas-public/features/editing/smart-split.js +48 -0
- package/dist/canvas-public/features/editing/source-preview.js +43 -0
- package/dist/canvas-public/features/editing/video-cycle.js +49 -0
- package/dist/canvas-public/features/editing/video-frames.js +35 -0
- package/dist/canvas-public/features/frame-editor/frame-editing.js +755 -0
- package/dist/canvas-public/features/frame-editor/frame-model.js +63 -0
- package/dist/canvas-public/features/frame-editor/playback.js +83 -0
- package/dist/canvas-public/features/frame-editor/preview.js +149 -0
- package/dist/canvas-public/features/generation/animation.js +272 -0
- package/dist/canvas-public/features/generation/asset-batch.js +102 -0
- package/dist/canvas-public/features/generation/direction.js +163 -0
- package/dist/canvas-public/features/generation/fields.js +73 -0
- package/dist/canvas-public/features/generation/game-ui.js +64 -0
- package/dist/canvas-public/features/generation/image.js +83 -0
- package/dist/canvas-public/features/generation/scroll-image.js +157 -0
- package/dist/canvas-public/features/generation/seamless-tile.js +52 -0
- package/dist/canvas-public/features/generation/tile-transition-atlas.js +79 -0
- package/dist/canvas-public/features/generation/video.js +105 -0
- package/dist/canvas-public/features/map/creation.js +73 -0
- package/dist/canvas-public/features/map/tile-map-editor.js +2518 -0
- package/dist/canvas-public/features/registry.js +59 -0
- package/dist/canvas-public/index.html +84 -259
- package/dist/canvas-public/inspector/controls.js +49 -0
- package/dist/canvas-public/inspector/frame-preview.js +11 -0
- package/dist/canvas-public/inspector/inspector.js +190 -0
- package/dist/canvas-public/inspector/media-inspector.js +517 -0
- package/dist/canvas-public/inspector/media-preview.js +56 -0
- package/dist/canvas-public/inspector/number-stepper.js +96 -0
- package/dist/canvas-public/inspector/resize.js +88 -0
- package/dist/canvas-public/inspector/scroll-preview.js +11 -0
- package/dist/canvas-public/inspector/ui-inspector.js +14 -0
- package/dist/canvas-public/language/en-US.js +1059 -0
- package/dist/canvas-public/language/index.js +79 -0
- package/dist/canvas-public/language/zh-CN.js +1059 -0
- package/dist/canvas-public/map/export.js +9 -0
- package/dist/canvas-public/map/inspector.js +8 -0
- package/dist/canvas-public/map/model.js +409 -0
- package/dist/canvas-public/map/tile-map-editor.js +2 -0
- package/dist/canvas-public/media/metadata.js +183 -0
- package/dist/canvas-public/media/support.js +183 -0
- package/dist/canvas-public/styles/base.css +49 -0
- package/dist/canvas-public/styles/canvas.css +127 -0
- package/dist/canvas-public/styles/controls.css +203 -0
- package/dist/canvas-public/styles/dialogs.css +521 -0
- package/dist/canvas-public/styles/feature-form.css +409 -0
- package/dist/canvas-public/styles/inspector.css +441 -0
- package/dist/canvas-public/styles/layout.css +344 -0
- package/dist/canvas-public/styles/map.css +11 -0
- package/dist/canvas-public/styles/nodes.css +636 -0
- package/dist/canvas-public/styles.css +9 -2247
- package/dist/canvas-public/ui/controls.js +222 -0
- package/dist/canvas-public/ui/elements.js +9 -0
- package/dist/canvas-public/ui/feature-controller.js +296 -0
- package/dist/canvas-public/ui/feature-form.js +401 -0
- package/dist/canvas-public/ui/settings.js +83 -0
- package/dist/canvas-public/ui/toolbar.js +55 -0
- package/dist/canvas-public/vendor/pixi-tilemap.js +126 -0
- package/dist/canvas-public/vendor/pixi.min.js +2340 -0
- package/dist/canvas-public/workers/color-key-worker.js +289 -0
- package/dist/canvas-public/workers/perfect-pixel-worker.js +510 -0
- package/dist/index.js +42236 -13382
- package/dist/skills/en-US/evomeme-canvas/SKILL.md +52 -0
- package/dist/skills/en-US/evomeme-canvas/agents/openai.yaml +7 -0
- package/dist/skills/en-US/evomeme-cloud-fs/SKILL.md +176 -0
- package/dist/skills/en-US/evomeme-cloud-fs/agents/openai.yaml +7 -0
- package/dist/skills/en-US/evomeme-cover/SKILL.md +132 -0
- package/dist/skills/en-US/evomeme-cover/agents/openai.yaml +7 -0
- package/dist/skills/en-US/evomeme-game-maker/SKILL.md +288 -0
- package/dist/skills/en-US/evomeme-game-maker/agents/openai.yaml +4 -0
- package/dist/skills/en-US/evomeme-login/SKILL.md +92 -0
- package/dist/skills/en-US/evomeme-login/agents/openai.yaml +7 -0
- package/dist/skills/en-US/evomeme-native-bridge-sdk/SKILL.md +286 -0
- package/dist/skills/en-US/evomeme-native-bridge-sdk/agents/openai.yaml +8 -0
- package/dist/skills/en-US/evomeme-tos-upload/SKILL.md +47 -0
- package/dist/skills/en-US/evomeme-tos-upload/agents/openai.yaml +5 -0
- package/dist/skills/en-US/evomeme-ui/SKILL.md +123 -0
- package/dist/skills/en-US/evomeme-ui/agents/openai.yaml +4 -0
- package/dist/skills/en-US/evomeme-ui/references/host-layout.md +69 -0
- package/dist/skills/en-US/game-art/SKILL.md +121 -0
- package/dist/skills/en-US/game-art/agents/openai.yaml +8 -0
- package/dist/skills/en-US/game-asset-align/SKILL.md +45 -0
- package/dist/skills/en-US/game-asset-align/agents/openai.yaml +8 -0
- package/dist/skills/en-US/game-asset-align/references/alignment-contract.md +61 -0
- package/dist/skills/en-US/game-asset-align/references/blender-scene-pipeline.md +45 -0
- package/dist/skills/en-US/game-asset-align/references/canonical-package.md +26 -0
- package/dist/skills/en-US/game-asset-align/references/engine-capabilities.md +14 -0
- package/dist/skills/en-US/game-asset-align/references/lightweight-review.md +32 -0
- package/dist/skills/en-US/game-asset-align/references/repair-policy.md +30 -0
- package/dist/skills/en-US/game-asset-align/references/tool-contract.md +39 -0
- package/dist/skills/en-US/game-asset-align/references/visual-review.md +26 -0
- package/dist/skills/en-US/game-asset-image-generator/SKILL.md +104 -0
- package/dist/skills/en-US/game-asset-image-generator/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-asset-image-generator/references/background.md +32 -0
- package/dist/skills/en-US/game-asset-image-generator/references/static-game-assets.md +38 -0
- package/dist/skills/en-US/game-audio/SKILL.md +82 -0
- package/dist/skills/en-US/game-audio/agents/openai.yaml +4 -0
- package/dist/skills/en-US/game-demo/SKILL.md +103 -0
- package/dist/skills/en-US/game-demo/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-maker/SKILL.md +111 -0
- package/dist/skills/en-US/game-maker/agents/openai.yaml +4 -0
- package/dist/skills/en-US/game-test/SKILL.md +58 -0
- package/dist/skills/en-US/game-test/agents/openai.yaml +7 -0
- package/dist/skills/en-US/game-ui/SKILL.md +177 -0
- package/dist/skills/en-US/game-ui/agents/openai.yaml +4 -0
- package/dist/skills/en-US/godot-3d-game/SKILL.md +39 -0
- package/dist/skills/en-US/godot-3d-game/agents/openai.yaml +8 -0
- package/dist/skills/en-US/godot-3d-game/references/alignment-gates.md +26 -0
- package/dist/skills/en-US/godot-3d-game/references/appearance-alignment.md +52 -0
- package/dist/skills/en-US/godot-3d-game/references/build-and-qa.md +22 -0
- package/dist/skills/en-US/godot-3d-game/references/canonical-asset-runtime.md +27 -0
- package/dist/skills/en-US/godot-3d-game/references/import-and-wrapper-scenes.md +20 -0
- package/dist/skills/en-US/godot-3d-game/references/physics-colliders.md +13 -0
- package/dist/skills/en-US/godot-3d-game/references/project-structure.md +22 -0
- package/dist/skills/en-US/godot-3d-game/references/skeleton-animation.md +16 -0
- package/dist/skills/en-US/phaser-2d-game/SKILL.md +127 -0
- package/dist/skills/en-US/phaser-2d-game/agents/openai.yaml +8 -0
- package/dist/skills/en-US/phaser-2d-game/references/engine-performance.md +64 -0
- package/dist/skills/en-US/phaser-2d-game/references/engine-physics.md +139 -0
- package/dist/skills/en-US/phaser-2d-game/references/engine-runtime.md +129 -0
- package/dist/skills/en-US/phaser-2d-game/references/engine-spritesheets.md +99 -0
- package/dist/skills/en-US/phaser-2d-game/references/engine-tilemaps.md +80 -0
- package/dist/skills/en-US/phaser-2d-game/references/file-structure.md +94 -0
- package/dist/skills/en-US/phaser-2d-graphics-builder/SKILL.md +68 -0
- package/dist/skills/en-US/phaser-2d-graphics-builder/agents/openai.yaml +7 -0
- package/dist/skills/en-US/threejs-3d-game/SKILL.md +117 -0
- package/dist/skills/en-US/threejs-3d-game/agents/openai.yaml +7 -0
- package/dist/skills/en-US/threejs-3d-game/references/engine-asset-pipeline.md +153 -0
- package/dist/skills/en-US/threejs-3d-game/references/engine-audio.md +70 -0
- package/dist/skills/en-US/threejs-3d-game/references/engine-input-camera.md +84 -0
- package/dist/skills/en-US/threejs-3d-game/references/engine-performance.md +96 -0
- package/dist/skills/en-US/threejs-3d-game/references/engine-physics.md +142 -0
- package/dist/skills/en-US/threejs-3d-game/references/engine-runtime.md +199 -0
- package/dist/skills/en-US/threejs-3d-game/references/file-structure.md +353 -0
- package/dist/skills/en-US/threejs-aaa-graphics-builder/SKILL.md +62 -0
- package/dist/skills/en-US/threejs-aaa-graphics-builder/agents/openai.yaml +7 -0
- package/{skills → dist/skills/zh-CN}/evomeme-cloud-fs/SKILL.md +4 -4
- package/{skills → dist/skills/zh-CN}/evomeme-cover/SKILL.md +1 -1
- package/{skills → dist/skills/zh-CN}/evomeme-game-maker/SKILL.md +91 -88
- package/{skills → dist/skills/zh-CN}/evomeme-tos-upload/SKILL.md +6 -5
- package/dist/skills/zh-CN/evomeme-ui/SKILL.md +123 -0
- package/dist/skills/zh-CN/evomeme-ui/agents/openai.yaml +4 -0
- package/dist/skills/zh-CN/evomeme-ui/references/host-layout.md +69 -0
- package/dist/skills/zh-CN/game-asset-align/SKILL.md +45 -0
- package/dist/skills/zh-CN/game-asset-align/agents/openai.yaml +8 -0
- package/dist/skills/zh-CN/game-asset-align/references/alignment-contract.md +61 -0
- package/dist/skills/zh-CN/game-asset-align/references/blender-scene-pipeline.md +45 -0
- package/dist/skills/zh-CN/game-asset-align/references/canonical-package.md +26 -0
- package/dist/skills/zh-CN/game-asset-align/references/engine-capabilities.md +14 -0
- package/dist/skills/zh-CN/game-asset-align/references/lightweight-review.md +32 -0
- package/dist/skills/zh-CN/game-asset-align/references/repair-policy.md +30 -0
- package/dist/skills/zh-CN/game-asset-align/references/tool-contract.md +39 -0
- package/dist/skills/zh-CN/game-asset-align/references/visual-review.md +26 -0
- package/{skills → dist/skills/zh-CN}/game-asset-image-generator/SKILL.md +1 -1
- package/dist/skills/zh-CN/game-audio/references/bgm-cdn.md +127 -0
- package/dist/skills/zh-CN/game-audio/references/sfx-cdn.md +77 -0
- package/dist/skills/zh-CN/game-test/references/checklists/playtest-qa.md +12 -0
- package/{skills → dist/skills/zh-CN}/game-ui/SKILL.md +9 -1
- package/dist/skills/zh-CN/game-ui/references/checklists/game-ui-quality.md +14 -0
- package/dist/skills/zh-CN/game-ui/references/checklists/hud-readability.md +12 -0
- package/dist/skills/zh-CN/game-ui/references/checklists/mobile-input.md +12 -0
- package/dist/skills/zh-CN/game-ui/references/checklists/responsive-ui-fit.md +12 -0
- package/dist/skills/zh-CN/game-ui/references/ui-patterns.md +140 -0
- package/dist/skills/zh-CN/game-ui/references/ui-prompt-templates.md +63 -0
- package/dist/skills/zh-CN/godot-3d-game/SKILL.md +39 -0
- package/dist/skills/zh-CN/godot-3d-game/agents/openai.yaml +8 -0
- package/dist/skills/zh-CN/godot-3d-game/references/alignment-gates.md +26 -0
- package/dist/skills/zh-CN/godot-3d-game/references/appearance-alignment.md +52 -0
- package/dist/skills/zh-CN/godot-3d-game/references/build-and-qa.md +22 -0
- package/dist/skills/zh-CN/godot-3d-game/references/canonical-asset-runtime.md +27 -0
- package/dist/skills/zh-CN/godot-3d-game/references/import-and-wrapper-scenes.md +20 -0
- package/dist/skills/zh-CN/godot-3d-game/references/physics-colliders.md +13 -0
- package/dist/skills/zh-CN/godot-3d-game/references/project-structure.md +22 -0
- package/dist/skills/zh-CN/godot-3d-game/references/skeleton-animation.md +16 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-1.jpeg +0 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-2.jpeg +0 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-3.jpg +0 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/checklists/compose-quality.md +10 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/checklists/performance-safe-visual-detail.md +9 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/checklists/premium-game-quality-gate.md +15 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/checklists/sprite-quality.md +10 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/checklists/technical-art-quality.md +9 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/checklists/visual-scorecard.md +9 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/compose-recipes.md +71 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/graphics-prompt-templates.md +33 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/implementation-blueprint.md +82 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/sprite-recipes.md +90 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/technical-art.md +67 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/vfx-recipes.md +67 -0
- package/dist/skills/zh-CN/phaser-2d-graphics-builder/references/visual-scorecard.md +129 -0
- package/{skills → dist/skills/zh-CN}/threejs-3d-game/SKILL.md +6 -2
- package/{skills → dist/skills/zh-CN}/threejs-3d-game/references/engine-asset-pipeline.md +52 -15
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-1.jpg +0 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-2.jpg +0 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-3.jpg +0 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/checklists/aaa-game-quality-gate.md +23 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/checklists/aaa-visual-scorecard.md +19 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/checklists/material-lighting-quality.md +14 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/checklists/performance-safe-visual-detail.md +14 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/checklists/procedural-model-quality.md +15 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/checklists/technical-art-quality.md +17 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/graphics-prompt-templates.md +236 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/implementation-blueprint.md +161 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/model-recipes.md +146 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/render-recipes.md +90 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/shader-cookbook.md +292 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/technical-art.md +167 -0
- package/dist/skills/zh-CN/threejs-aaa-graphics-builder/references/visual-scorecard.md +147 -0
- package/dist/stage-blender/bake_materials.py +102 -0
- package/dist/stage-blender/export_scene.py +275 -0
- package/dist/stage-blender/preview_camera.py +90 -0
- package/dist/stage-blender/repair_mesh.py +112 -0
- package/dist/stage-public/app.js +759 -0
- package/dist/stage-public/baked/bakePilot.js +44 -0
- package/dist/stage-public/baked/bakedScene.js +129 -0
- package/dist/stage-public/baked/sceneAppearance.js +225 -0
- package/dist/stage-public/baked/sceneCamera.js +45 -0
- package/dist/stage-public/comparison-contract.js +28 -0
- package/dist/stage-public/delivery.html +4 -0
- package/dist/stage-public/delivery.js +17 -0
- package/dist/stage-public/godot/appearance.gd +170 -0
- package/dist/stage-public/godot/preview.gd +79 -0
- package/dist/stage-public/index.html +141 -0
- package/dist/stage-public/review-app.js +289 -0
- package/dist/stage-public/review-runtime.js +63 -0
- package/dist/stage-public/review.css +24 -0
- package/dist/stage-public/review.html +47 -0
- package/dist/stage-public/scene-runtime.js +212 -0
- package/dist/stage-public/sceneContractRuntime.js +235 -0
- package/dist/stage-public/styles.css +328 -0
- package/dist/stage-public/three-appearance.js +104 -0
- package/dist/stage-public/vendor/three170/LICENSE +21 -0
- package/dist/stage-public/vendor/three170/build/three.module.js +54571 -0
- package/dist/stage-public/vendor/three170/examples/jsm/controls/OrbitControls.js +1523 -0
- package/dist/stage-public/vendor/three170/examples/jsm/environments/RoomEnvironment.js +144 -0
- package/dist/stage-public/vendor/three170/examples/jsm/libs/fflate.module.js +2672 -0
- package/dist/stage-public/vendor/three170/examples/jsm/loaders/EXRLoader.js +2563 -0
- package/dist/stage-public/vendor/three170/examples/jsm/loaders/GLTFLoader.js +4727 -0
- package/dist/stage-public/vendor/three170/examples/jsm/utils/BufferGeometryUtils.js +1373 -0
- package/dist/tile-transition-mask.svg +40 -0
- package/package.json +21 -7
- package/dist/canvas-public/cycle-detect.js +0 -166
- package/dist/canvas-public/smart-split.js +0 -185
- /package/{skills → dist/skills/en-US}/game-audio/references/bgm-cdn.md +0 -0
- /package/{skills → dist/skills/en-US}/game-audio/references/sfx-cdn.md +0 -0
- /package/{skills → dist/skills/en-US}/game-test/references/checklists/playtest-qa.md +0 -0
- /package/{skills → dist/skills/en-US}/game-ui/references/checklists/game-ui-quality.md +0 -0
- /package/{skills → dist/skills/en-US}/game-ui/references/checklists/hud-readability.md +0 -0
- /package/{skills → dist/skills/en-US}/game-ui/references/checklists/mobile-input.md +0 -0
- /package/{skills → dist/skills/en-US}/game-ui/references/checklists/responsive-ui-fit.md +0 -0
- /package/{skills → dist/skills/en-US}/game-ui/references/ui-patterns.md +0 -0
- /package/{skills → dist/skills/en-US}/game-ui/references/ui-prompt-templates.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-1.jpeg +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-2.jpeg +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/assets/scorecard-anchors/scene-3.jpg +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/checklists/compose-quality.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/checklists/performance-safe-visual-detail.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/checklists/premium-game-quality-gate.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/checklists/sprite-quality.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/checklists/technical-art-quality.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/checklists/visual-scorecard.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/compose-recipes.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/graphics-prompt-templates.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/implementation-blueprint.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/sprite-recipes.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/technical-art.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/vfx-recipes.md +0 -0
- /package/{skills → dist/skills/en-US}/phaser-2d-graphics-builder/references/visual-scorecard.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-1.jpg +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-2.jpg +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/assets/scorecard-anchors/scene-3.jpg +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/checklists/aaa-game-quality-gate.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/checklists/aaa-visual-scorecard.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/checklists/material-lighting-quality.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/checklists/performance-safe-visual-detail.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/checklists/procedural-model-quality.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/checklists/technical-art-quality.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/graphics-prompt-templates.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/implementation-blueprint.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/model-recipes.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/render-recipes.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/shader-cookbook.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/technical-art.md +0 -0
- /package/{skills → dist/skills/en-US}/threejs-aaa-graphics-builder/references/visual-scorecard.md +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-canvas/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-canvas/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-cloud-fs/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-cover/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-game-maker/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-login/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-login/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-native-bridge-sdk/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-native-bridge-sdk/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/evomeme-tos-upload/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/game-art/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/game-art/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/game-asset-image-generator/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/game-asset-image-generator/references/background.md +0 -0
- /package/{skills → dist/skills/zh-CN}/game-asset-image-generator/references/static-game-assets.md +0 -0
- /package/{skills → dist/skills/zh-CN}/game-audio/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/game-audio/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/game-demo/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/game-demo/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/game-maker/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/game-maker/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/game-test/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/game-test/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/game-ui/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/references/engine-performance.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/references/engine-physics.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/references/engine-runtime.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/references/engine-spritesheets.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/references/engine-tilemaps.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-game/references/file-structure.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-graphics-builder/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/phaser-2d-graphics-builder/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-3d-game/agents/openai.yaml +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-3d-game/references/engine-audio.md +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-3d-game/references/engine-input-camera.md +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-3d-game/references/engine-performance.md +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-3d-game/references/engine-physics.md +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-3d-game/references/engine-runtime.md +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-3d-game/references/file-structure.md +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-aaa-graphics-builder/SKILL.md +0 -0
- /package/{skills → dist/skills/zh-CN}/threejs-aaa-graphics-builder/agents/openai.yaml +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
3
|
+
import { batchStaticScene } from './sceneAppearance.js';
|
|
4
|
+
|
|
5
|
+
export function annotateSourceNames(gltf) {
|
|
6
|
+
gltf.scene.traverse(object => {
|
|
7
|
+
const index = gltf.parser.associations.get(object)?.nodes;
|
|
8
|
+
if (index !== undefined) object.userData.canonicalSourceName = gltf.parser.json.nodes[index].name;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function loadBakePilot(manifest, readVerified, renderer) {
|
|
13
|
+
if (manifest.schema !== 'evomeme.cycles_bake_pilot.v1' || manifest.display.runtime_tone_mapped !== false || manifest.display.runtime_lighting !== false) throw new Error('Unsupported bake display contract');
|
|
14
|
+
const model = await readVerified('runtime/bake-pilot/pilot.glb');
|
|
15
|
+
const gltf = await new GLTFLoader().parseAsync(model, '');
|
|
16
|
+
const textures = new Map();
|
|
17
|
+
try {
|
|
18
|
+
for (const group of manifest.groups) {
|
|
19
|
+
const bytes = await readVerified(group.display_texture);
|
|
20
|
+
const url = URL.createObjectURL(new Blob([bytes], { type: 'image/png' }));
|
|
21
|
+
let texture;
|
|
22
|
+
try { texture = await new THREE.TextureLoader().loadAsync(url); } finally { URL.revokeObjectURL(url); }
|
|
23
|
+
texture.colorSpace = THREE.SRGBColorSpace;
|
|
24
|
+
texture.flipY = false;
|
|
25
|
+
texture.anisotropy = Math.min(8, renderer.capabilities.getMaxAnisotropy());
|
|
26
|
+
textures.set(group.id, texture);
|
|
27
|
+
}
|
|
28
|
+
gltf.scene.traverse(object => {
|
|
29
|
+
if (!object.isMesh) return;
|
|
30
|
+
let id;
|
|
31
|
+
for (let node = object; node; node = node.parent) if (node.userData.bake_group) { id = node.userData.bake_group; break; }
|
|
32
|
+
if (!textures.has(id) || !object.geometry.attributes.uv) throw new Error('Pilot mesh has no atlas mapping');
|
|
33
|
+
object.material.dispose();
|
|
34
|
+
object.material = new THREE.MeshBasicMaterial({ name: `Cycles baked diffuse | ${id}`, map: textures.get(id), toneMapped: false, fog: false });
|
|
35
|
+
});
|
|
36
|
+
const batch = batchStaticScene(gltf.scene);
|
|
37
|
+
batch.root.name = 'cycles_bake_pilot_identity_root';
|
|
38
|
+
batch.root.traverse(object => { if (object.isMesh) { object.castShadow = true; object.receiveShadow = false; } });
|
|
39
|
+
return { root: batch.root, stats: batch.sourceStats, dispose() {
|
|
40
|
+
batch.root.traverse(o => { o.geometry?.dispose(); o.material?.dispose(); });
|
|
41
|
+
textures.forEach(t => t.dispose());
|
|
42
|
+
} };
|
|
43
|
+
} catch (error) { textures.forEach(t => t.dispose()); throw error; }
|
|
44
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';
|
|
3
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
4
|
+
import { batchStaticScene } from './sceneAppearance.js';
|
|
5
|
+
|
|
6
|
+
export function exrTexture(bytes, atlas = false) {
|
|
7
|
+
const d = new EXRLoader().parse(bytes);
|
|
8
|
+
const t = new THREE.DataTexture(d.data,d.width,d.height,d.format,d.type);
|
|
9
|
+
t.colorSpace = THREE.LinearSRGBColorSpace;
|
|
10
|
+
t.minFilter = THREE.LinearMipmapLinearFilter; t.magFilter = THREE.LinearFilter;
|
|
11
|
+
t.generateMipmaps = true;
|
|
12
|
+
// EXRLoader returns bottom-first rows; glTF reverses Blender's original UV V.
|
|
13
|
+
if (atlas) { t.repeat.y = -1; t.offset.y = 1; }
|
|
14
|
+
t.needsUpdate = true;
|
|
15
|
+
return t;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function loadBakedScene(manifest, readVerified, renderer) {
|
|
19
|
+
if (manifest.schema !== 'evomeme.cycles_baked_scene.v1' || manifest.display.texture_color_space !== 'Linear Rec.709') throw new Error('Unsupported linear scene bake');
|
|
20
|
+
const ownedTextures = [], ownedMaterials = new Set();
|
|
21
|
+
let root;
|
|
22
|
+
try {
|
|
23
|
+
const gltf = await new GLTFLoader().parseAsync(await readVerified(manifest.model_file),'');
|
|
24
|
+
const groupMaterials = new Map();
|
|
25
|
+
for (const group of manifest.groups) {
|
|
26
|
+
let map = null;
|
|
27
|
+
if (group.encoding === 'linear_exr') {
|
|
28
|
+
map = exrTexture(await readVerified(group.linear_master),true);
|
|
29
|
+
map.anisotropy = Math.min(8,renderer.capabilities.getMaxAnisotropy());
|
|
30
|
+
ownedTextures.push(map);
|
|
31
|
+
} else if (!group.encoding.startsWith('linear_vertex_color')) throw new Error('Unsupported surface encoding');
|
|
32
|
+
const material = new THREE.MeshBasicMaterial({name:`Cycles scene | ${group.id}`,map,vertexColors:group.encoding.startsWith('linear_vertex_color'),toneMapped:false,fog:false,side:THREE.DoubleSide});
|
|
33
|
+
if(group.encoding==='linear_vertex_color_two_sided'){
|
|
34
|
+
material.onBeforeCompile=shader=>{
|
|
35
|
+
shader.vertexShader='attribute vec3 _backlight;varying vec3 vBackLight;\n'+shader.vertexShader;
|
|
36
|
+
shader.vertexShader=shader.vertexShader.replace('#include <begin_vertex>','#include <begin_vertex>\nvBackLight=_backlight;');
|
|
37
|
+
shader.fragmentShader='varying vec3 vBackLight;\n'+shader.fragmentShader;
|
|
38
|
+
shader.fragmentShader=shader.fragmentShader.replace('#include <color_fragment>','#include <color_fragment>\nif(!gl_FrontFacing) diffuseColor.rgb=vBackLight;');
|
|
39
|
+
};
|
|
40
|
+
material.customProgramCacheKey=()=> 'baked-grass-two-sided-v1';
|
|
41
|
+
}
|
|
42
|
+
ownedMaterials.add(material); groupMaterials.set(group.id,material);
|
|
43
|
+
}
|
|
44
|
+
gltf.scene.traverse(o => {
|
|
45
|
+
if (!o.isMesh) return;
|
|
46
|
+
let id;
|
|
47
|
+
for (let p=o;p;p=p.parent) if (p.userData.bake_group) {id=p.userData.bake_group;break;}
|
|
48
|
+
const m=groupMaterials.get(id);
|
|
49
|
+
if (!m || (m.map && !o.geometry.attributes.uv) || (m.vertexColors && !o.geometry.attributes.color)) throw new Error(`Missing baked channel: ${id}`);
|
|
50
|
+
o.material.dispose(); o.material=m;
|
|
51
|
+
});
|
|
52
|
+
const batch=batchStaticScene(gltf.scene); root=batch.root;
|
|
53
|
+
root.name='full_baked_scene_identity_root';
|
|
54
|
+
root.traverse(o=>{o.castShadow=false;o.receiveShadow=false;});
|
|
55
|
+
const sky=exrTexture(await readVerified(manifest.atmosphere.sky_file)); ownedTextures.push(sky);
|
|
56
|
+
const lutUrl=URL.createObjectURL(new Blob([await readVerified(manifest.atmosphere.display_lut.file)],{type:'image/png'}));
|
|
57
|
+
let lut;
|
|
58
|
+
try {lut=await new THREE.TextureLoader().loadAsync(lutUrl);} finally {URL.revokeObjectURL(lutUrl);}
|
|
59
|
+
lut.colorSpace=THREE.NoColorSpace;lut.flipY=true;lut.generateMipmaps=false;
|
|
60
|
+
lut.minFilter=THREE.LinearFilter;lut.magFilter=THREE.LinearFilter;ownedTextures.push(lut);
|
|
61
|
+
const skyMaterial=new THREE.ShaderMaterial({side:THREE.BackSide,depthWrite:false,toneMapped:false,
|
|
62
|
+
uniforms:{radiance:{value:sky}},
|
|
63
|
+
vertexShader:'varying vec3 direction;void main(){direction=position;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);}',
|
|
64
|
+
fragmentShader:'varying vec3 direction;uniform sampler2D radiance;void main(){vec3 d=normalize(direction);vec2 uv=vec2(atan(d.x,-d.z)/6.28318530718+.5,asin(clamp(d.y,-1.,1.))/3.14159265359+.5);gl_FragColor=vec4(texture2D(radiance,uv).rgb,1.);}'
|
|
65
|
+
});
|
|
66
|
+
const skyMesh=new THREE.Mesh(new THREE.SphereGeometry(340,40,24),skyMaterial);
|
|
67
|
+
skyMesh.frustumCulled=false;skyMesh.renderOrder=-100;skyMesh.name='Cycles authored radiance sky';
|
|
68
|
+
return {root,sky:skyMesh,lut,stats:batch.sourceStats,update(camera){skyMesh.position.copy(camera.position);},dispose(){
|
|
69
|
+
root.traverse(o=>o.geometry?.dispose());skyMesh.geometry.dispose();skyMaterial.dispose();
|
|
70
|
+
ownedMaterials.forEach(m=>m.dispose());ownedTextures.forEach(t=>t.dispose());
|
|
71
|
+
}};
|
|
72
|
+
} catch(error) {root?.traverse(o=>o.geometry?.dispose());ownedMaterials.forEach(m=>m.dispose());ownedTextures.forEach(t=>t.dispose());throw error;}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Single scene-linear HDR pass, bounded depth fog, then the source Blender display LUT. */
|
|
76
|
+
export function createBakedPipeline(renderer, baked, manifest) {
|
|
77
|
+
const target=new THREE.WebGLRenderTarget(1,1,{type:THREE.HalfFloatType,format:THREE.RGBAFormat,samples:4});
|
|
78
|
+
target.depthTexture=new THREE.DepthTexture(1,1,THREE.UnsignedIntType);
|
|
79
|
+
const postScene=new THREE.Scene();
|
|
80
|
+
const postCamera=new THREE.OrthographicCamera(-1,1,1,-1,0,1);
|
|
81
|
+
const a=manifest.atmosphere;
|
|
82
|
+
const mist=a.local_mist||[];
|
|
83
|
+
const uniforms={source:{value:target.texture},depth:{value:target.depthTexture},lut:{value:baked.lut},
|
|
84
|
+
invProjection:{value:new THREE.Matrix4()},cameraWorld:{value:new THREE.Matrix4()},cameraPositionWorld:{value:new THREE.Vector3()},
|
|
85
|
+
fogMin:{value:new THREE.Vector3().fromArray(a.fog.bounds_min)},fogMax:{value:new THREE.Vector3().fromArray(a.fog.bounds_max)},
|
|
86
|
+
fogDensity:{value:a.fog.density},fogLight:{value:new THREE.Color().fromArray(a.fog.inscatter_linear)},
|
|
87
|
+
lutSize:{value:a.display_lut.size},lutMax:{value:a.display_lut.domain_max},saturation:{value:a.compositor.saturation},
|
|
88
|
+
mistTransform:{value:mist.map(m=>new THREE.Matrix4().fromArray(m.world_to_unit))},mistDensity:{value:mist.map(m=>m.mean_density)},
|
|
89
|
+
mistLight:{value:mist.map(m=>new THREE.Vector3(...m.color_linear.map((c,i)=>c/a.fog.color_linear[i]*a.fog.inscatter_linear[i])))},
|
|
90
|
+
texel:{value:new THREE.Vector2()},glowThreshold:{value:a.compositor.glow_threshold},glowStrength:{value:a.compositor.glow_strength},displayCheck:{value:false},surfaceOnly:{value:false}};
|
|
91
|
+
const material=new THREE.ShaderMaterial({depthTest:false,depthWrite:false,toneMapped:false,uniforms,
|
|
92
|
+
vertexShader:'varying vec2 vUv;void main(){vUv=uv;gl_Position=vec4(position.xy,0.,1.);}',
|
|
93
|
+
fragmentShader:`varying vec2 vUv;uniform sampler2D source,depth,lut;uniform mat4 invProjection,cameraWorld;uniform vec3 cameraPositionWorld,fogMin,fogMax,fogLight;
|
|
94
|
+
uniform mat4 mistTransform[${Math.max(1,mist.length)}];uniform float mistDensity[${Math.max(1,mist.length)}];uniform vec3 mistLight[${Math.max(1,mist.length)}];
|
|
95
|
+
uniform float fogDensity,lutSize,lutMax,saturation,glowThreshold,glowStrength;uniform vec2 texel;uniform bool displayCheck,surfaceOnly;
|
|
96
|
+
vec3 sourceView(vec3 c){vec3 q=sqrt(clamp(c/lutMax,0.,1.))*(lutSize-1.);float b=floor(q.b);float next=min(b+1.,lutSize-1.);
|
|
97
|
+
vec2 uv0=vec2((q.r+b*lutSize+.5)/(lutSize*lutSize),(q.g+.5)/lutSize);
|
|
98
|
+
vec2 uv1=vec2((q.r+next*lutSize+.5)/(lutSize*lutSize),(q.g+.5)/lutSize);
|
|
99
|
+
return mix(texture2D(lut,uv0).rgb,texture2D(lut,uv1).rgb,fract(q.b));}
|
|
100
|
+
vec3 saturateHSV(vec3 c,float s){float hi=max(c.r,max(c.g,c.b));return max(vec3(0.),vec3(hi)+(c-vec3(hi))*s);}
|
|
101
|
+
void main(){vec3 c=texture2D(source,vUv).rgb;float z=texture2D(depth,vUv).r;
|
|
102
|
+
if(!surfaceOnly && z<.999999){vec4 v=invProjection*vec4(vUv*2.-1.,z*2.-1.,1.);v/=v.w;vec3 p=(cameraWorld*v).xyz;
|
|
103
|
+
vec3 delta=p-cameraPositionWorld;float distance=length(delta);vec3 d=delta/max(distance,.00001);
|
|
104
|
+
vec3 safeD=mix(vec3(-1.),vec3(1.),step(vec3(0.),d))*max(abs(d),vec3(.00001));vec3 t0=(fogMin-cameraPositionWorld)/safeD;vec3 t1=(fogMax-cameraPositionWorld)/safeD;
|
|
105
|
+
vec3 lo=min(t0,t1),hi=max(t0,t1);float enter=max(0.,max(lo.x,max(lo.y,lo.z)));float leave=min(distance,min(hi.x,min(hi.y,hi.z)));
|
|
106
|
+
float travel=max(0.,leave-enter);float T=exp(-fogDensity*travel);c=c*T+fogLight*(1.-T);
|
|
107
|
+
for(int i=0;i<${mist.length};i++){
|
|
108
|
+
vec3 origin=(mistTransform[i]*vec4(cameraPositionWorld,1.)).xyz;vec3 ray=(mistTransform[i]*vec4(d,0.)).xyz;
|
|
109
|
+
float A=dot(ray,ray),B=dot(origin,ray),C=dot(origin,origin)-1.;float det=B*B-A*C;
|
|
110
|
+
if(det>0.){float root=sqrt(det);float entry=max(0.,(-B-root)/A);float exit=min(distance,(-B+root)/A);
|
|
111
|
+
float span=max(0.,exit-entry);float localT=exp(-mistDensity[i]*span);c=c*localT+mistLight[i]*(1.-localT);}}
|
|
112
|
+
}
|
|
113
|
+
if(!surfaceOnly){vec3 glow=vec3(0.);for(int x=-2;x<=2;x++){for(int y=-2;y<=2;y++){
|
|
114
|
+
vec3 s=texture2D(source,vUv+vec2(float(x),float(y))*texel*3.).rgb;glow+=max(s-vec3(glowThreshold),vec3(0.));}}
|
|
115
|
+
c+=glow*glowStrength/25.;c=saturateHSV(c,saturation);}
|
|
116
|
+
if(displayCheck){int i=int(floor(vUv.x*8.));if(i==0)c=vec3(0.);else if(i==1)c=vec3(.002,.004,.008);else if(i==2)c=vec3(.02,.05,.1);else if(i==3)c=vec3(.1,.2,.3);else if(i==4)c=vec3(.5);else if(i==5)c=vec3(1.);else if(i==6)c=vec3(2.,1.,.5);else c=vec3(4.,8.,16.);}
|
|
117
|
+
gl_FragColor=vec4(sourceView(c),1.);}`});
|
|
118
|
+
const quad=new THREE.Mesh(new THREE.PlaneGeometry(2,2),material);postScene.add(quad);
|
|
119
|
+
const size=new THREE.Vector2();
|
|
120
|
+
return {render(scene,camera,{displayCheck=false,surfaceOnly=false}={}){
|
|
121
|
+
renderer.getDrawingBufferSize(size);
|
|
122
|
+
if(target.width!==size.x||target.height!==size.y)target.setSize(size.x,size.y);
|
|
123
|
+
uniforms.invProjection.value.copy(camera.projectionMatrixInverse);uniforms.cameraWorld.value.copy(camera.matrixWorld);
|
|
124
|
+
uniforms.cameraPositionWorld.value.copy(camera.position);uniforms.texel.value.set(1/size.x,1/size.y);uniforms.displayCheck.value=displayCheck;uniforms.surfaceOnly.value=surfaceOnly;
|
|
125
|
+
const autoReset=renderer.info.autoReset;renderer.info.autoReset=false;renderer.info.reset();
|
|
126
|
+
try {renderer.setRenderTarget(target);renderer.render(scene,camera);renderer.setRenderTarget(null);renderer.render(postScene,postCamera);}
|
|
127
|
+
finally {renderer.setRenderTarget(null);renderer.info.autoReset=autoReset;}
|
|
128
|
+
},dispose(){target.dispose();quad.geometry.dispose();material.dispose();}};
|
|
129
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
|
|
3
|
+
import { blenderLightDirection } from './sceneCamera.js';
|
|
4
|
+
|
|
5
|
+
const noiseGLSL = `
|
|
6
|
+
float hash31(vec3 p){ p=fract(p*0.1031); p+=dot(p,p.yzx+33.33); return fract((p.x+p.y)*p.z); }
|
|
7
|
+
float noise3(vec3 x){ vec3 i=floor(x), f=fract(x); f=f*f*(3.0-2.0*f);
|
|
8
|
+
return mix(mix(mix(hash31(i),hash31(i+vec3(1,0,0)),f.x),mix(hash31(i+vec3(0,1,0)),hash31(i+vec3(1,1,0)),f.x),f.y),
|
|
9
|
+
mix(mix(hash31(i+vec3(0,0,1)),hash31(i+vec3(1,0,1)),f.x),mix(hash31(i+vec3(0,1,1)),hash31(i+vec3(1,1,1)),f.x),f.y),f.z); }
|
|
10
|
+
float fbm(vec3 p){ return noise3(p)*0.53+noise3(p*2.03+17.1)*0.27+noise3(p*4.11+5.3)*0.13+noise3(p*8.21)*0.07; }
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
function randomSequence(seed) {
|
|
14
|
+
return () => { seed |= 0; seed = seed + 0x6D2B79F5 | 0; let t = Math.imul(seed ^ seed >>> 15, 1 | seed); t ^= t + Math.imul(t ^ t >>> 7, 61 | t); return ((t ^ t >>> 14) >>> 0) / 4294967296; };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Static batching transforms cloned vertices, never the canonical root or source file. */
|
|
18
|
+
export function batchStaticScene(source, { splitSourceNames = [], splitSceneNames = [] } = {}) {
|
|
19
|
+
source.updateMatrixWorld(true);
|
|
20
|
+
const selected = new Set(splitSourceNames);
|
|
21
|
+
const sceneSelected = new Set(splitSceneNames);
|
|
22
|
+
const groups = new Map();
|
|
23
|
+
let meshes = 0, triangles = 0;
|
|
24
|
+
source.traverse(object => {
|
|
25
|
+
if (!object.isMesh) return;
|
|
26
|
+
if (object.isSkinnedMesh || Array.isArray(object.material)) throw new Error('Scene batcher expects static single-material GLB meshes');
|
|
27
|
+
meshes++;
|
|
28
|
+
triangles += (object.geometry.index?.count || object.geometry.attributes.position.count) / 3;
|
|
29
|
+
const geometry = object.geometry.index ? object.geometry.toNonIndexed() : object.geometry.clone();
|
|
30
|
+
geometry.applyMatrix4(object.matrixWorld);
|
|
31
|
+
// UV channels and tangents are part of the render contract, including baked atlases.
|
|
32
|
+
for (const attribute of Object.keys(geometry.attributes)) if (!['position', 'normal', 'color', 'uv', 'uv1', 'uv2', 'uv3', 'tangent', '_backlight'].includes(attribute)) geometry.deleteAttribute(attribute);
|
|
33
|
+
let pilotSource = false, bakedSceneSource = false;
|
|
34
|
+
for (let parent = object; parent; parent = parent.parent) {
|
|
35
|
+
if (selected.has(parent.userData.canonicalSourceName)) pilotSource = true;
|
|
36
|
+
if (sceneSelected.has(parent.userData.canonicalSourceName)) bakedSceneSource = true;
|
|
37
|
+
}
|
|
38
|
+
const key = object.material.uuid + Object.keys(geometry.attributes).sort().join(',') + pilotSource + bakedSceneSource;
|
|
39
|
+
if (!groups.has(key)) groups.set(key, { material: object.material, geometries: [], names: [], pilotSource, bakedSceneSource });
|
|
40
|
+
groups.get(key).geometries.push(geometry);
|
|
41
|
+
groups.get(key).names.push(object.name);
|
|
42
|
+
});
|
|
43
|
+
const root = new THREE.Group();
|
|
44
|
+
root.name = 'canonical_identity_root';
|
|
45
|
+
for (const { material, geometries, names, pilotSource, bakedSceneSource } of groups.values()) {
|
|
46
|
+
const geometry = mergeGeometries(geometries, false);
|
|
47
|
+
if (!geometry) throw new Error(`Cannot batch ${material.name}`);
|
|
48
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
49
|
+
mesh.name = material.name;
|
|
50
|
+
mesh.userData.sourceNodes = names;
|
|
51
|
+
mesh.userData.bakePilotSource = pilotSource;
|
|
52
|
+
mesh.userData.bakedSceneSource = bakedSceneSource;
|
|
53
|
+
mesh.receiveShadow = true;
|
|
54
|
+
// Low-poly distant cliffs receive architectural shadows; disabling their
|
|
55
|
+
// huge self-caster avoids shadow-map acne without changing their geometry.
|
|
56
|
+
mesh.castShadow = !/Meadow|Airborne|Atmospheric far|Distant summit|Distant blue schist/i.test(material.name);
|
|
57
|
+
root.add(mesh);
|
|
58
|
+
geometries.forEach(g => g.dispose());
|
|
59
|
+
}
|
|
60
|
+
source.traverse(o => o.geometry?.dispose());
|
|
61
|
+
return { root, sourceStats: { meshes, triangles, batches: groups.size } };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createAppearance(scene, root, renderer, contract, profile) {
|
|
65
|
+
if (profile.schema !== 'evomeme.three_scene_profile.v1' || profile.asset_id !== contract.asset_id) throw new Error('Unsupported or mismatched Three.js appearance profile');
|
|
66
|
+
const time = { value: 0 };
|
|
67
|
+
const p = profile;
|
|
68
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
69
|
+
renderer.toneMapping = THREE.AgXToneMapping;
|
|
70
|
+
renderer.toneMappingExposure = p.render.exposure;
|
|
71
|
+
renderer.shadowMap.enabled = true;
|
|
72
|
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
73
|
+
renderer.shadowMap.autoUpdate = false;
|
|
74
|
+
renderer.shadowMap.needsUpdate = true;
|
|
75
|
+
scene.fog = new THREE.Fog(p.atmosphere.fog_color, p.atmosphere.fog_near, p.atmosphere.fog_far);
|
|
76
|
+
const atmosphere = new THREE.Group();
|
|
77
|
+
atmosphere.name = 'contract_runtime_atmosphere';
|
|
78
|
+
scene.add(atmosphere);
|
|
79
|
+
|
|
80
|
+
const sunEntry = contract.blender_lights.find(l => l.type === 'SUN');
|
|
81
|
+
if (!sunEntry) throw new Error('Contract has no authored SUN');
|
|
82
|
+
const sun = new THREE.DirectionalLight(new THREE.Color().fromArray(sunEntry.color_linear), p.lighting.sun_intensity);
|
|
83
|
+
const sunDirection = blenderLightDirection(sunEntry.rotation_xyz_rad);
|
|
84
|
+
sun.target.position.fromArray(p.lighting.sun_target);
|
|
85
|
+
sun.position.copy(sun.target.position).addScaledVector(sunDirection, -130);
|
|
86
|
+
sun.castShadow = true;
|
|
87
|
+
const extent = p.lighting.shadow_extent;
|
|
88
|
+
Object.assign(sun.shadow.camera, { left: -extent, right: extent, top: extent, bottom: -extent, near: 1, far: 270 });
|
|
89
|
+
sun.shadow.mapSize.setScalar(p.render.shadow_map_size);
|
|
90
|
+
sun.shadow.normalBias = .10;
|
|
91
|
+
sun.shadow.bias = -.00012;
|
|
92
|
+
sun.shadow.camera.updateProjectionMatrix();
|
|
93
|
+
atmosphere.add(sun, sun.target);
|
|
94
|
+
atmosphere.add(new THREE.HemisphereLight(p.lighting.sky_color, p.lighting.ground_color, p.lighting.hemisphere_intensity));
|
|
95
|
+
const fill = new THREE.DirectionalLight(p.lighting.fill_color, p.lighting.fill_intensity);
|
|
96
|
+
fill.position.fromArray(p.lighting.fill_position);
|
|
97
|
+
fill.target.position.set(0, 4, -12);
|
|
98
|
+
atmosphere.add(fill, fill.target);
|
|
99
|
+
|
|
100
|
+
const skyMaterial = new THREE.ShaderMaterial({
|
|
101
|
+
side: THREE.BackSide, depthWrite: false, fog: false,
|
|
102
|
+
uniforms: {
|
|
103
|
+
uTime: time,
|
|
104
|
+
uLow: { value: new THREE.Color().fromArray(p.atmosphere.sky_low_linear) },
|
|
105
|
+
uHigh: { value: new THREE.Color().fromArray(p.atmosphere.sky_high_linear) },
|
|
106
|
+
uShadow: { value: new THREE.Color().fromArray(p.atmosphere.cloud_shadow_linear) },
|
|
107
|
+
uLight: { value: new THREE.Color().fromArray(p.atmosphere.cloud_light_linear) },
|
|
108
|
+
uScale: { value: p.atmosphere.cloud_scale }, uSpeed: { value: p.atmosphere.cloud_speed }
|
|
109
|
+
},
|
|
110
|
+
vertexShader: `varying vec3 vDirection; void main(){ vDirection=position; gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0); }`,
|
|
111
|
+
fragmentShader: `varying vec3 vDirection; uniform float uTime,uScale,uSpeed; uniform vec3 uLow,uHigh,uShadow,uLight;
|
|
112
|
+
${noiseGLSL}
|
|
113
|
+
void main(){
|
|
114
|
+
vec3 d=normalize(vDirection); vec3 q=d*vec3(1.0,1.8,1.0)*uScale+vec3(uTime*uSpeed,0.0,1.7);
|
|
115
|
+
float n=fbm(q+fbm(q*0.6)*1.8);
|
|
116
|
+
float cloud=smoothstep(0.38,0.70,n);
|
|
117
|
+
vec3 sky=mix(uLow,uHigh,smoothstep(0.0,0.8,d.y));
|
|
118
|
+
vec3 bank=mix(uShadow,uLight,smoothstep(0.40,0.73,n));
|
|
119
|
+
// A soft cloud tower behind the western gate, in world direction space.
|
|
120
|
+
float tower=exp(-pow((d.x+0.22)/0.09,2.0)-pow((d.y-0.26)/0.27,2.0));
|
|
121
|
+
tower*=smoothstep(0.3,0.64,fbm(q*1.4+2.0));
|
|
122
|
+
sky=mix(sky,bank,cloud*.92); sky=mix(sky,uLight,tower*.62);
|
|
123
|
+
gl_FragColor=vec4(sky,1.0);
|
|
124
|
+
#include <tonemapping_fragment>
|
|
125
|
+
#include <colorspace_fragment>
|
|
126
|
+
}`
|
|
127
|
+
});
|
|
128
|
+
const sky = new THREE.Mesh(new THREE.SphereGeometry(340, 32, 20), skyMaterial);
|
|
129
|
+
sky.frustumCulled = false;
|
|
130
|
+
sky.renderOrder = -100;
|
|
131
|
+
atmosphere.add(sky);
|
|
132
|
+
|
|
133
|
+
const materialSet = new Set();
|
|
134
|
+
root.traverse(o => { if (o.isMesh) materialSet.add(o.material); });
|
|
135
|
+
for (const material of materialSet) {
|
|
136
|
+
const stone = /Limestone|limestone|slate|schist|cliffs|stone joints/.test(material.name);
|
|
137
|
+
const meadow = /Meadow|soil/.test(material.name);
|
|
138
|
+
const cliff = /schist|cliffs/.test(material.name);
|
|
139
|
+
if (cliff) material.color.multiplyScalar(p.materials.cliff_value);
|
|
140
|
+
material.roughness = .91;
|
|
141
|
+
if (material.name.includes('Airborne')) { material.emissiveIntensity = .6; continue; }
|
|
142
|
+
material.onBeforeCompile = shader => {
|
|
143
|
+
Object.assign(shader.uniforms, { uTime: time, uHaze: { value: new THREE.Color(p.atmosphere.fog_color) } });
|
|
144
|
+
shader.vertexShader = 'varying vec3 vSceneWorld;\n' + shader.vertexShader;
|
|
145
|
+
shader.vertexShader = shader.vertexShader.replace('#include <begin_vertex>', '#include <begin_vertex>\nvSceneWorld=(modelMatrix*vec4(position,1.0)).xyz;');
|
|
146
|
+
shader.fragmentShader = `varying vec3 vSceneWorld; uniform float uTime; uniform vec3 uHaze;\n${noiseGLSL}\n` + shader.fragmentShader;
|
|
147
|
+
shader.fragmentShader = shader.fragmentShader.replace('#include <color_fragment>', `#include <color_fragment>
|
|
148
|
+
float surfaceNoise=fbm(vSceneWorld*${Number(p.materials.stone_scale).toFixed(3)});
|
|
149
|
+
diffuseColor.rgb*=1.0+(surfaceNoise-.5)*${stone ? p.materials.stone_variation * 2 : p.materials.grass_variation};
|
|
150
|
+
${stone ? 'float grain=noise3(vSceneWorld*25.0); diffuseColor.rgb*=.95+grain*.1;' : ''}
|
|
151
|
+
${meadow ? `float cloudMask=exp(-pow((vSceneWorld.x+23.0)/18.0,2.0)-pow((vSceneWorld.z-4.0)/27.0,2.0)); diffuseColor.rgb*=1.0-cloudMask*${p.materials.cloud_shadow_strength};` : ''}
|
|
152
|
+
`);
|
|
153
|
+
shader.fragmentShader = shader.fragmentShader.replace('#include <opaque_fragment>', `
|
|
154
|
+
float heightHaze=exp(-max(vSceneWorld.y,0.0)*${p.atmosphere.height_haze_falloff})*smoothstep(40.0,135.0,length(vViewPosition))*${p.atmosphere.height_haze_strength};
|
|
155
|
+
outgoingLight=mix(outgoingLight,uHaze,heightHaze);
|
|
156
|
+
#include <opaque_fragment>`);
|
|
157
|
+
shader.fragmentShader = shader.fragmentShader.replace('#include <tonemapping_fragment>', `#include <tonemapping_fragment>
|
|
158
|
+
float luminance=dot(gl_FragColor.rgb,vec3(.2126,.7152,.0722));
|
|
159
|
+
gl_FragColor.rgb=max(vec3(0.0),mix(vec3(luminance),gl_FragColor.rgb,${p.render.saturation}));
|
|
160
|
+
`);
|
|
161
|
+
};
|
|
162
|
+
material.customProgramCacheKey = () => `sanctuary-v2-${stone}-${meadow}`;
|
|
163
|
+
material.needsUpdate = true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const effects = new THREE.Group();
|
|
167
|
+
effects.name = 'light_threads_and_wind';
|
|
168
|
+
atmosphere.add(effects);
|
|
169
|
+
const threadMaterial = new THREE.MeshBasicMaterial({ color: p.effects.thread_color, transparent: true, opacity: p.effects.thread_opacity, depthWrite: false, toneMapped: false });
|
|
170
|
+
const groundRay = new THREE.Raycaster();
|
|
171
|
+
const meadowMesh = root.children.find(o => o.name.includes('Meadow ·'));
|
|
172
|
+
root.updateMatrixWorld(true);
|
|
173
|
+
for (const t of p.effects.light_threads) {
|
|
174
|
+
const base = new THREE.Vector3().fromArray(t.base);
|
|
175
|
+
if (meadowMesh) {
|
|
176
|
+
groundRay.set(new THREE.Vector3(base.x, 70, base.z), new THREE.Vector3(0, -1, 0));
|
|
177
|
+
const hit = groundRay.intersectObject(meadowMesh)[0];
|
|
178
|
+
if (hit) base.y = hit.point.y + .3;
|
|
179
|
+
}
|
|
180
|
+
const curve = new THREE.CatmullRomCurve3([base, base.clone().add(new THREE.Vector3(-.03, t.height * .53, 0)), base.clone().add(new THREE.Vector3(.1, t.height, 0))]);
|
|
181
|
+
effects.add(new THREE.Mesh(new THREE.TubeGeometry(curve, 12, t.radius, 4, false), threadMaterial));
|
|
182
|
+
}
|
|
183
|
+
const windMaterial = new THREE.MeshBasicMaterial({ color: '#d2e7d6', transparent: true, opacity: p.effects.wind_opacity, depthWrite: false, toneMapped: false });
|
|
184
|
+
for (const points of p.effects.wind_paths) {
|
|
185
|
+
const curve = new THREE.CatmullRomCurve3(points.map(p => new THREE.Vector3().fromArray(p)));
|
|
186
|
+
effects.add(new THREE.Mesh(new THREE.TubeGeometry(curve, 70, .018, 3, false), windMaterial));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const random = randomSequence(p.seed);
|
|
190
|
+
const count = p.effects.mote_count;
|
|
191
|
+
const positions = new Float32Array(count * 3);
|
|
192
|
+
for (let i = 0; i < count; i++) positions.set([(random() - .5) * 80, 3 + random() * 27, 15 - random() * 90], i * 3);
|
|
193
|
+
const moteGeometry = new THREE.BufferGeometry();
|
|
194
|
+
moteGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
195
|
+
const moteMaterial = new THREE.ShaderMaterial({
|
|
196
|
+
transparent: true, depthWrite: false,
|
|
197
|
+
uniforms: { uTime: time, uDpr: { value: renderer.getPixelRatio() } },
|
|
198
|
+
vertexShader: `uniform float uTime,uDpr; varying float vOpacity; void main(){ vec3 p=position; p.x+=sin(uTime*.16+position.z)*.6; p.y+=sin(uTime*.31+position.x)*.18; vec4 mv=modelViewMatrix*vec4(p,1.0); gl_Position=projectionMatrix*mv; gl_PointSize=clamp(100.0/max(6.0,-mv.z),1.0,3.0)*uDpr; vOpacity=.3+.25*sin(position.x+uTime*.8); }`,
|
|
199
|
+
fragmentShader: `varying float vOpacity; void main(){ vec2 uv=gl_PointCoord-.5; float a=1.0-smoothstep(.2,.5,length(uv*vec2(.8,1.5))); gl_FragColor=vec4(.86,.94,.85,a*vOpacity); }`
|
|
200
|
+
});
|
|
201
|
+
effects.add(new THREE.Points(moteGeometry, moteMaterial));
|
|
202
|
+
return {
|
|
203
|
+
sunDirection: sunDirection.toArray(),
|
|
204
|
+
focusCharacterShadow(position, localExtent = 4) {
|
|
205
|
+
// A tight source-sun shadow map resolves the character on the static baked plaza.
|
|
206
|
+
const extent = position ? localExtent : p.lighting.shadow_extent;
|
|
207
|
+
if (position) sun.target.position.copy(position);
|
|
208
|
+
else sun.target.position.fromArray(p.lighting.sun_target);
|
|
209
|
+
sun.position.copy(sun.target.position).addScaledVector(sunDirection, -130);
|
|
210
|
+
Object.assign(sun.shadow.camera,{left:-extent,right:extent,top:extent,bottom:-extent});
|
|
211
|
+
sun.shadow.normalBias = position ? .008 : .10;
|
|
212
|
+
sun.shadow.camera.updateProjectionMatrix();
|
|
213
|
+
renderer.shadowMap.needsUpdate = true;
|
|
214
|
+
},
|
|
215
|
+
setIsolated(isolated) { sky.visible = !isolated; effects.visible = !isolated; },
|
|
216
|
+
setAuthoredSky(enabled) { sky.visible = !enabled; },
|
|
217
|
+
update(seconds, camera) { time.value = seconds; sky.position.copy(camera.position); threadMaterial.opacity = p.effects.thread_opacity * (.95 + .05 * Math.sin(seconds * .6)); moteMaterial.uniforms.uDpr.value = renderer.getPixelRatio(); },
|
|
218
|
+
dispose() {
|
|
219
|
+
scene.remove(atmosphere);
|
|
220
|
+
const geometries = new Set(), materials = new Set();
|
|
221
|
+
atmosphere.traverse(o => { if (o.geometry) geometries.add(o.geometry); if (o.material) materials.add(o.material); });
|
|
222
|
+
geometries.forEach(g => g.dispose()); materials.forEach(m => m.dispose()); sun.shadow.dispose();
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
export function applySceneCamera(camera, controls, definition) {
|
|
4
|
+
if (!definition) throw new Error('Missing contract camera');
|
|
5
|
+
camera.position.fromArray(definition.position);
|
|
6
|
+
camera.quaternion.fromArray(definition.quaternion_xyzw).normalize();
|
|
7
|
+
camera.up.fromArray(definition.up || [0, 1, 0]);
|
|
8
|
+
camera.fov = definition.vertical_fov_rad * 180 / Math.PI;
|
|
9
|
+
camera.aspect = definition.aspect_ratio;
|
|
10
|
+
camera.near = definition.near;
|
|
11
|
+
camera.far = definition.far;
|
|
12
|
+
camera.updateProjectionMatrix();
|
|
13
|
+
camera.updateMatrixWorld(true);
|
|
14
|
+
if (controls) controls.target.fromArray(definition.look_at);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function fitFrame(width, height, aspect) {
|
|
18
|
+
const w = Math.min(width, height * aspect);
|
|
19
|
+
return { width: Math.max(1, w), height: Math.max(1, w / aspect) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function inspectScene(root, camera, contract, cameraId) {
|
|
23
|
+
root.updateMatrixWorld(true);
|
|
24
|
+
const near = (a, b, tolerance = 0.0001) => a.length === b.length && a.every((v, i) => Number.isFinite(v) && Math.abs(v - b[i]) < tolerance);
|
|
25
|
+
const box = new THREE.Box3().setFromObject(root, true);
|
|
26
|
+
const c = contract.cameras.find(c => c.id === cameraId);
|
|
27
|
+
const identity = new THREE.Matrix4();
|
|
28
|
+
const results = [
|
|
29
|
+
{ name: '根矩阵保持原点 / 米制 / 原轴向', passed: near(root.matrixWorld.elements, identity.elements) },
|
|
30
|
+
{ name: '实际 GLB 包围盒符合源契约', passed: near(box.min.toArray(), contract.coordinates.bounds_gltf_metres.min, .002) && near(box.max.toArray(), contract.coordinates.bounds_gltf_metres.max, .002) }
|
|
31
|
+
];
|
|
32
|
+
if (c) {
|
|
33
|
+
const q = new THREE.Quaternion().fromArray(c.quaternion_xyzw).normalize();
|
|
34
|
+
results.push({ name: `${c.id} 相机位置及旋转`, passed: near(camera.position.toArray(), c.position) && 1 - Math.abs(camera.quaternion.dot(q)) < 1e-7 });
|
|
35
|
+
results.push({ name: '投影 / 裁剪面 / 1.88 画幅', passed: near([camera.fov, camera.aspect, camera.near, camera.far], [c.vertical_fov_degrees, c.aspect_ratio, c.near, c.far]) });
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Blender Euler XYZ applies fixed X, then Y, then Z: Rz * Ry * Rx.
|
|
41
|
+
// Three's intrinsic ZYX produces that matrix (intrinsic XYZ does not).
|
|
42
|
+
export function blenderLightDirection(rotation) {
|
|
43
|
+
const d = new THREE.Vector3(0, 0, -1).applyEuler(new THREE.Euler(...rotation, 'ZYX'));
|
|
44
|
+
return new THREE.Vector3(d.x, d.z, -d.y).normalize();
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const close = (actual, expected, tolerance = .001) => Array.isArray(actual) && Array.isArray(expected) && actual.length === expected.length && actual.every((v, i) => Number.isFinite(v) && Number.isFinite(expected[i]) && Math.abs(v - expected[i]) <= tolerance);
|
|
2
|
+
const result = (state, label, detail) => ({ state, label, detail });
|
|
3
|
+
|
|
4
|
+
// Check only requirements actually declared by this asset. No inferred gameplay requirements.
|
|
5
|
+
export function compareSpatial(contract, stats, recipe) {
|
|
6
|
+
const c = contract?.coordinates;
|
|
7
|
+
if (!c) return result('warn', '未声明约束', '资产契约未声明空间要求。');
|
|
8
|
+
const failures = [];
|
|
9
|
+
if (c.root_transform) {
|
|
10
|
+
if (!close(recipe.translation_metres, c.root_transform.position) || !close(recipe.scale_xyz, c.root_transform.scale)) failures.push('根位置或尺寸');
|
|
11
|
+
const q = c.root_transform.quaternion_xyzw;
|
|
12
|
+
if (!close(recipe.rotation_quaternion_xyzw, q) && !close(recipe.rotation_quaternion_xyzw, q?.map(v => -v))) failures.push('根旋转');
|
|
13
|
+
}
|
|
14
|
+
if (c.bounds_gltf_metres) for (const key of ['min', 'max']) if (!close(stats.bounds[key], c.bounds_gltf_metres[key], .01)) failures.push(`场景边界 ${key}`);
|
|
15
|
+
if (failures.length) return result('error', '存在偏差', `${failures.join('、')}与契约不符。`);
|
|
16
|
+
if (!c.root_transform && !c.bounds_gltf_metres) return result('warn', '部分已检查', '轴向元数据已检查;契约未声明根变换或边界。');
|
|
17
|
+
return result('ok', '符合声明约束', `${stats.size.map(n => n.toFixed(2)).join(' × ')} m · 保留场景原点`);
|
|
18
|
+
}
|
|
19
|
+
export function compareStructural(contract, stats) {
|
|
20
|
+
const contents = contract?.contents;
|
|
21
|
+
if (!contents) return result('warn', '未声明数量', `已载入 ${stats.meshes} 网格;契约未声明结构数量。`);
|
|
22
|
+
const mappings = { mesh_count: 'meshes', node_count: 'nodes', material_count: 'materials', animations: 'animations', camera_count: 'cameras', skin_count: 'skins' };
|
|
23
|
+
const declared = Object.entries(mappings).filter(([k]) => typeof contents[k] === 'number');
|
|
24
|
+
const failures = declared.filter(([k, stat]) => contents[k] !== stats[stat]).map(([k]) => k);
|
|
25
|
+
if (failures.length) return result('error', '存在偏差', `与契约不符:${failures.join('、')}`);
|
|
26
|
+
if (!declared.length) return result('warn', '未声明数量', '已载入原始层级,尚无可比对的结构数量约束。');
|
|
27
|
+
return result('ok', '符合声明数量', `${stats.nodes} 节点 · ${stats.meshes} 网格 · ${stats.materials} 材质`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>已确认资产 · Three.js</title>
|
|
2
|
+
<style>html,body{margin:0;background:#151d20;color:#cce2dc;font:14px system-ui;height:100%}header{height:50px;display:flex;align-items:center;justify-content:space-between;padding:0 24px;border-bottom:1px solid #344044}#view{height:calc(100% - 51px);display:grid;place-items:center}.render-frame canvas{display:block}a{color:#a5dec9}</style>
|
|
3
|
+
<script type="importmap">{"imports":{"three":"./vendor/three170/build/three.module.js","three/addons/":"./vendor/three170/examples/jsm/"}}</script>
|
|
4
|
+
<header><strong id="title">读取已确认资产…</strong><span id="status">校验版本与显示资源</span><a href="./HANDOFF.md">交互设计接入说明</a></header><div id="view"></div><script type="module" src="./delivery.js"></script></html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createReviewView } from './review-runtime.js';
|
|
2
|
+
try {
|
|
3
|
+
const delivery = await fetch('./delivery.json').then(r => r.json());
|
|
4
|
+
const bytes = await Promise.all(Object.entries(delivery.integrity).map(async ([file, expected]) => {
|
|
5
|
+
const r = await fetch(`./${file}`); if (!r.ok) throw new Error(`资源缺失:${file}`);
|
|
6
|
+
const data = await r.arrayBuffer();
|
|
7
|
+
const hash = [...new Uint8Array(await crypto.subtle.digest('SHA-256', data))].map(n => n.toString(16).padStart(2, '0')).join('');
|
|
8
|
+
if (hash !== expected) throw new Error(`交付文件已变化:${file}`); return [file, ['confirmation.json', 'asset/asset.json', 'asset/scene.contract.json'].includes(file) ? data : null];
|
|
9
|
+
}));
|
|
10
|
+
const resources = Object.fromEntries(bytes), json = key => JSON.parse(new TextDecoder().decode(resources[key]));
|
|
11
|
+
const record = json('confirmation.json');
|
|
12
|
+
const view = await createReviewView({ container: document.querySelector('#view'), assetBase: new URL('./asset/', location.href), asset: json('asset/asset.json'), contract: resources['asset/scene.contract.json'] ? json('asset/scene.contract.json') : null,
|
|
13
|
+
files: record.files, presentation: delivery.presentation, profile: record.target_profile, onProgress: message => document.querySelector('#status').textContent = message });
|
|
14
|
+
document.querySelector('#title').textContent = `${delivery.asset_id} · 已确认版本`;
|
|
15
|
+
document.querySelector('#status').textContent = '同一显示实现 · 可拖动观察 · 交互尚未设计';
|
|
16
|
+
window.addEventListener('pagehide', () => view.dispose());
|
|
17
|
+
} catch (error) { document.querySelector('#status').textContent = error.message; console.error(error); }
|