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,289 @@
|
|
|
1
|
+
import { createReviewView } from './review-runtime.js';
|
|
2
|
+
import { compareSpatial, compareStructural } from './comparison-contract.js';
|
|
3
|
+
|
|
4
|
+
const $ = id => document.getElementById(id);
|
|
5
|
+
// The task chooses the scene. Never enumerate packages or substitute another asset.
|
|
6
|
+
const assetId = new URLSearchParams(location.search).get('asset');
|
|
7
|
+
let view, check, cameraChoices = [], aspect = 1.88, busy = false, referenceUrl, cameraEpoch = 0;
|
|
8
|
+
let renderTimer, renderEpoch = 0, currentJob, interacting = false, needsRender = false;
|
|
9
|
+
const apiBase = `/api/packages/${encodeURIComponent(assetId)}`;
|
|
10
|
+
let targetEngine = new URLSearchParams(location.search).get('engine') === 'godot' ? 'godot' : 'three_js';
|
|
11
|
+
let stageView, viewedCapture, godotUrl, hasReference = false;
|
|
12
|
+
$('target-engine').value = targetEngine;
|
|
13
|
+
const engineName = () => targetEngine === 'godot' ? 'Godot' : 'Three.js';
|
|
14
|
+
function renderAdaptation() {
|
|
15
|
+
const report = check.adaptation?.[targetEngine], panel = $('adaptation-panel');
|
|
16
|
+
panel.hidden = !report;
|
|
17
|
+
if (!report) return;
|
|
18
|
+
const labels = {mapped:'已映射',baked:'已烘焙',approximate:'采用近似',missing:'尚未实现',unverified:'待核对'};
|
|
19
|
+
$('adaptation-summary').textContent = `${report.summary.missing || 0} 项尚未实现 · ${report.summary.approximate || 0} 项采用近似`;
|
|
20
|
+
const source = check.contract?.source_features?.rendering;
|
|
21
|
+
$('adaptation-scope').textContent = `${engineName()} · ${report.renderer} | Blender ${source?.engine || '源设置未记录'} · ${source?.preview_quality === 'final' ? '源采样参考图' : '快速参考图'}。自由视角使用快速原生渲染。`;
|
|
22
|
+
$('adaptation-items').replaceChildren();
|
|
23
|
+
for (const item of report.items) {
|
|
24
|
+
const row = document.createElement('div'); row.className = 'adaptation-row';
|
|
25
|
+
const title = document.createElement('div'); title.className = 'adaptation-title';
|
|
26
|
+
const name = document.createElement('strong'); name.textContent = item.label;
|
|
27
|
+
const badge = document.createElement('span'); badge.className = `adaptation-badge ${item.status}`; badge.textContent = labels[item.status];
|
|
28
|
+
title.append(name,badge);
|
|
29
|
+
const description = document.createElement('p'); description.textContent = item.detail;
|
|
30
|
+
const action = document.createElement('p'); action.className = 'adaptation-action'; action.textContent = item.action;
|
|
31
|
+
const buttons = document.createElement('div'); buttons.className = 'adaptation-buttons';
|
|
32
|
+
const inspect = document.createElement('button'); inspect.textContent = '查看相关机位';
|
|
33
|
+
inspect.onclick = () => {
|
|
34
|
+
if (busy) return;
|
|
35
|
+
const index = cameraChoices.findIndex(c => c.camera?.id === item.camera_id);
|
|
36
|
+
$('camera').value = String(Math.max(index,0));
|
|
37
|
+
setCamera(Math.max(index,0)).catch(failed);
|
|
38
|
+
document.querySelector('.viewer').scrollIntoView({behavior:'smooth',block:'start'});
|
|
39
|
+
};
|
|
40
|
+
const request = document.createElement('button'); request.textContent = '记录修复要求';
|
|
41
|
+
request.onclick = async () => {
|
|
42
|
+
if (busy || !view || !stageView) { message('当前画面就绪后可记录修复要求。'); return; }
|
|
43
|
+
request.disabled = true;
|
|
44
|
+
try {
|
|
45
|
+
await post('feedback',{revision:check.revision,presentation:view.capture().presentation,
|
|
46
|
+
object_name:(item.object_names || []).join(', '),instruction:`目标 ${engineName()},修复「${item.label}」:${item.detail} ${item.action}`});
|
|
47
|
+
request.textContent = '已记录,回对话继续'; message('修复要求已保存在当前资产中;回到建模对话继续处理。');
|
|
48
|
+
} catch(error) { message(error.message); request.disabled = false; }
|
|
49
|
+
};
|
|
50
|
+
buttons.append(inspect,request); row.append(title,description,action,buttons); $('adaptation-items').append(row);
|
|
51
|
+
}
|
|
52
|
+
panel.open = report.summary.missing > 0;
|
|
53
|
+
}
|
|
54
|
+
function resetConfirmation() { stageView = null; viewedCapture = null; $('confirm-asset').disabled = true; $('confirmation-state').textContent = '当前画面待确认'; document.querySelector('.confirmation-bar').classList.remove('confirmed'); }
|
|
55
|
+
async function post(endpoint, body) {
|
|
56
|
+
const response = await fetch(`${apiBase}/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
57
|
+
const result = await response.json(); if (!response.ok || !result.ok) throw new Error(result.error || 'Stage 请求失败'); return result;
|
|
58
|
+
}
|
|
59
|
+
async function finishView() {
|
|
60
|
+
const epoch = renderEpoch, engine = targetEngine;
|
|
61
|
+
resetConfirmation();
|
|
62
|
+
const capture = view.capture(); let native;
|
|
63
|
+
if (engine === 'godot') {
|
|
64
|
+
$('godot-image').hidden = true; $('godot-pending').hidden = false; $('godot-progress').textContent = 'Godot 正在渲染当前机位…';
|
|
65
|
+
try {
|
|
66
|
+
native = await post('godot-preview', { revision: check.revision, presentation: capture.presentation });
|
|
67
|
+
if (epoch !== renderEpoch || engine !== targetEngine) return;
|
|
68
|
+
const image = await verifiedImage(native.image_url,native);
|
|
69
|
+
if (epoch !== renderEpoch) { URL.revokeObjectURL(image.url); return; }
|
|
70
|
+
if (godotUrl) URL.revokeObjectURL(godotUrl); godotUrl = image.url;
|
|
71
|
+
$('godot-image').src = image.url; $('godot-image').hidden = false; $('godot-pending').hidden = true;
|
|
72
|
+
capture.screenshot = await fetch(image.url).then(r => r.blob()).then(blob => new Promise(resolve => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.readAsDataURL(blob); }));
|
|
73
|
+
$('engine-render-label').textContent = `原生 ${native.native.version}`;
|
|
74
|
+
$('confirmation-note').textContent = [hasReference ? '请核对 Godot 的材质、灯光和阴影,再确认当前版本。' : '未提供 Blender 基准;只确认目标引擎效果,不代表与 Blender 画面一致。',...native.profile.warnings].join(' ');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (epoch === renderEpoch) { $('godot-progress').textContent = error.message; message(error.message); }
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (epoch !== renderEpoch || engine !== targetEngine) return;
|
|
81
|
+
const receipt = await post('stage-view', { revision: check.revision, presentation: capture.presentation, engine, godot_preview_id: native?.id });
|
|
82
|
+
if (epoch !== renderEpoch || engine !== targetEngine) return;
|
|
83
|
+
stageView = receipt; viewedCapture = capture;
|
|
84
|
+
if (!hasReference) checkState('appearance', {state:'warn',label:'目标引擎已载入',detail:'未提供 Blender 基准;请查看当前材质与灯光效果。'});
|
|
85
|
+
$('confirmation-state').textContent = `${engineName()} 当前版本待用户确认`;
|
|
86
|
+
$('confirm-asset').disabled = false;
|
|
87
|
+
}
|
|
88
|
+
const message = text => { $('status').textContent = text; $('loading-text').textContent = text; };
|
|
89
|
+
function checkState(kind, value) { $(`${kind}-state`).className = `check-state ${value.state}`; $(`${kind}-state`).textContent = value.label; $(`${kind}-detail`).textContent = value.detail; }
|
|
90
|
+
function split(value) {
|
|
91
|
+
const n = Math.max(0, Math.min(100, value));
|
|
92
|
+
$('comparison').style.setProperty('--split', `${n}%`);
|
|
93
|
+
$('divider').setAttribute('aria-valuenow', String(Math.round(n)));
|
|
94
|
+
$('divider').setAttribute('aria-valuetext', `左侧 Blender ${Math.round(n)}%,右侧 ${engineName()} ${Math.round(100 - n)}%`);
|
|
95
|
+
document.querySelector('.source-label').hidden = n === 0;
|
|
96
|
+
document.querySelector('.engine-label').hidden = n === 100;
|
|
97
|
+
}
|
|
98
|
+
function fit() {
|
|
99
|
+
const w = Math.min($('stage').clientWidth, $('stage').clientHeight * aspect);
|
|
100
|
+
$('comparison').style.width = `${w}px`; $('comparison').style.height = `${w / aspect}px`;
|
|
101
|
+
}
|
|
102
|
+
const resizeObserver = new ResizeObserver(fit); resizeObserver.observe($('stage'));
|
|
103
|
+
function drag(event) { const r = $('comparison').getBoundingClientRect(); split((event.clientX - r.left) / r.width * 100); }
|
|
104
|
+
$('divider').addEventListener('pointerdown', event => { event.stopPropagation(); if (event.button !== 0 || busy) return; event.preventDefault(); $('divider').focus({ preventScroll: true }); $('divider').setPointerCapture(event.pointerId); drag(event); });
|
|
105
|
+
$('divider').addEventListener('pointermove', event => { event.stopPropagation(); if ($('divider').hasPointerCapture(event.pointerId)) drag(event); });
|
|
106
|
+
for (const type of ['pointerup', 'pointercancel']) $('divider').addEventListener(type, event => { event.stopPropagation(); if ($('divider').hasPointerCapture(event.pointerId)) $('divider').releasePointerCapture(event.pointerId); });
|
|
107
|
+
$('divider').addEventListener('keydown', event => {
|
|
108
|
+
const current = Number($('divider').getAttribute('aria-valuenow'));
|
|
109
|
+
const values = { ArrowLeft: current - 2, ArrowDown: current - 2, ArrowRight: current + 2, ArrowUp: current + 2, Home: 0, End: 100 };
|
|
110
|
+
if (event.key in values) { event.preventDefault(); split(values[event.key]); }
|
|
111
|
+
});
|
|
112
|
+
async function verifiedImage(file, fingerprint) {
|
|
113
|
+
const response = await fetch(fingerprint ? file : `${apiBase}/files/${file}?revision=${check.revision}`);
|
|
114
|
+
if (!response.ok) throw new Error('Blender 基准读取失败');
|
|
115
|
+
const bytes = await response.arrayBuffer(), expected = fingerprint || check.files[file];
|
|
116
|
+
const hash = [...new Uint8Array(await crypto.subtle.digest('SHA-256', bytes))].map(n => n.toString(16).padStart(2, '0')).join('');
|
|
117
|
+
if (!expected || hash !== expected.sha256 || bytes.byteLength !== expected.bytes) throw new Error('Blender 基准已变化,请重新载入场景');
|
|
118
|
+
const url = URL.createObjectURL(new Blob([bytes], { type: 'image/png' })), img = new Image(); img.src = url;
|
|
119
|
+
try { await img.decode(); } catch (error) { URL.revokeObjectURL(url); throw error; }
|
|
120
|
+
return { url, width: img.naturalWidth, height: img.naturalHeight };
|
|
121
|
+
}
|
|
122
|
+
function cancelJob(id) { if (id) fetch(`${apiBase}/blender-preview/${id}/cancel`, { method: 'POST', keepalive: true }).catch(() => {}); }
|
|
123
|
+
function invalidatePreview() { ++renderEpoch; clearTimeout(renderTimer); cancelJob(currentJob); currentJob = null; needsRender = false; resetConfirmation(); if (targetEngine === 'godot') { $('godot-image').hidden = true; $('godot-pending').hidden = false; $('godot-progress').textContent = '当前机位待更新…'; } }
|
|
124
|
+
function nativePending(text, error = false) {
|
|
125
|
+
$('blender-view').classList.add('pending'); $('blender-pending').hidden = false;
|
|
126
|
+
$('blender-pending').classList.toggle('error', error); $('blender-progress').textContent = text;
|
|
127
|
+
$('retry-preview').hidden = !error; $('view-state').textContent = error ? 'Blender 视角更新失败' : '正在同步当前视角';
|
|
128
|
+
$('blender-label').textContent = error ? '当前视角未就绪' : '正在更新机位';
|
|
129
|
+
checkState('appearance', { state: 'warn', label: '当前视角待更新', detail: 'Three.js 已更新;等待同机位 Blender 原生渲染。' });
|
|
130
|
+
}
|
|
131
|
+
function nativeReady(image, label) {
|
|
132
|
+
if (referenceUrl) URL.revokeObjectURL(referenceUrl);
|
|
133
|
+
referenceUrl = image.url; $('blender-image').src = image.url;
|
|
134
|
+
$('blender-view').classList.remove('pending'); $('blender-pending').hidden = true;
|
|
135
|
+
$('blender-label').textContent = label; $('view-state').textContent = '对齐后的实际效果';
|
|
136
|
+
checkState('appearance', { state: 'warn', label: '显示已应用 · 待比对', detail: view.mode === 'baked' ? '已应用源烘焙与显示配方 · 拖动对比渲染差异' : '已应用目标引擎材质 · 拖动对比渲染差异' });
|
|
137
|
+
}
|
|
138
|
+
function cameraChanged() {
|
|
139
|
+
if (busy || !view) return;
|
|
140
|
+
invalidatePreview(); needsRender = true; $('camera').value = 'free';
|
|
141
|
+
nativePending(interacting ? '松开后更新 Blender 视角' : '准备渲染当前视角…');
|
|
142
|
+
$('camera-note').textContent = '自由视角 · 共享相机';
|
|
143
|
+
message('Three.js 实时响应;Blender 停止操作后按同一机位重新渲染。');
|
|
144
|
+
if (!interacting) schedulePreview();
|
|
145
|
+
}
|
|
146
|
+
function schedulePreview() { clearTimeout(renderTimer); if (needsRender) renderTimer = setTimeout(renderPreview, 450); }
|
|
147
|
+
async function renderPreview() {
|
|
148
|
+
if (!view || busy || interacting) return;
|
|
149
|
+
if (!hasReference) {
|
|
150
|
+
needsRender = false;
|
|
151
|
+
try { await finishView(); } catch (error) { message(error.message); }
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
needsRender = false;
|
|
155
|
+
const epoch = renderEpoch, revision = check.revision, camera = view.getCamera();
|
|
156
|
+
nativePending('Blender 正在渲染当前视角…');
|
|
157
|
+
const started = performance.now();
|
|
158
|
+
try {
|
|
159
|
+
const response = await fetch(`${apiBase}/blender-preview`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ revision, camera }) });
|
|
160
|
+
let job = await response.json();
|
|
161
|
+
if (epoch !== renderEpoch) { if (job.id) cancelJob(job.id); return; }
|
|
162
|
+
if (!response.ok || !job.ok) throw new Error(job.error || 'Blender 预览请求失败');
|
|
163
|
+
currentJob = job.id;
|
|
164
|
+
while (job.state === 'queued' || job.state === 'rendering') {
|
|
165
|
+
if (epoch !== renderEpoch) return;
|
|
166
|
+
const seconds = Math.round((performance.now() - started) / 1000);
|
|
167
|
+
$('blender-progress').textContent = `${job.state === 'queued' ? '等待上一帧结束' : 'Blender 正在渲染'} · ${seconds} 秒`;
|
|
168
|
+
await new Promise(resolve => setTimeout(resolve, 700));
|
|
169
|
+
if (epoch !== renderEpoch) return;
|
|
170
|
+
const result = await fetch(`${apiBase}/blender-preview/${job.id}`); job = await result.json();
|
|
171
|
+
if (!result.ok || !job.ok) throw new Error(job.error || '无法读取 Blender 渲染进度');
|
|
172
|
+
}
|
|
173
|
+
if (epoch !== renderEpoch) return;
|
|
174
|
+
if (job.state !== 'ready') throw new Error(job.error || '该视角已被替代,请重试当前视角');
|
|
175
|
+
if (job.revision !== revision || JSON.stringify(job.camera) !== JSON.stringify(camera)) throw new Error('Blender 返回机位与当前视角不一致');
|
|
176
|
+
const image = await verifiedImage(job.image_url, job);
|
|
177
|
+
if (epoch !== renderEpoch) { URL.revokeObjectURL(image.url); return; }
|
|
178
|
+
if (Math.abs(image.width / image.height - aspect) > .003) { URL.revokeObjectURL(image.url); throw new Error('Blender 返回画幅与当前机位不一致'); }
|
|
179
|
+
nativeReady(image, `Blender · ${job.receipt.samples} samples 预览`);
|
|
180
|
+
await finishView();
|
|
181
|
+
$('camera-note').textContent = `自由视角 · ${image.width} × ${image.height} · ${job.receipt.seconds} 秒`;
|
|
182
|
+
message('两侧已同步到当前机位。Blender 为原生快速预览;可继续旋转、平移、缩放或拖动分隔线。');
|
|
183
|
+
currentJob = null;
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (epoch !== renderEpoch) return;
|
|
186
|
+
nativePending('当前视角未生成,可重试或切回参考机位。', true); message(error.message); currentJob = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
$('retry-preview').addEventListener('pointerdown', e => e.stopPropagation());
|
|
190
|
+
$('retry-preview').onclick = () => { invalidatePreview(); renderPreview(); };
|
|
191
|
+
async function setCamera(index) {
|
|
192
|
+
const choice = cameraChoices[index], epoch = ++cameraEpoch;
|
|
193
|
+
if (!choice) return;
|
|
194
|
+
invalidatePreview(); view?.setInteractive(false);
|
|
195
|
+
busy = true; $('camera').disabled = true; $('reload').disabled = true; $('target-engine').disabled = true; $('comparison').hidden = true; $('loading').hidden = false;
|
|
196
|
+
$('loading').classList.remove('error');
|
|
197
|
+
message('对齐 Blender 与 Three.js 的机位和画幅…');
|
|
198
|
+
try {
|
|
199
|
+
if (!choice.file) {
|
|
200
|
+
view.resetCamera(); aspect = view.getCamera().aspect; fit(); split(0);
|
|
201
|
+
$('comparison').hidden = false; $('loading').hidden = true;
|
|
202
|
+
$('view-state').textContent = '目标引擎实际预览'; $('camera-note').textContent = '未提供 Blender 基准,当前只核对目标引擎效果';
|
|
203
|
+
$('confirmation-note').textContent = '未提供 Blender 基准。请查看目标引擎实际材质与灯光;此确认不代表与 Blender 画面一致。';
|
|
204
|
+
await finishView();
|
|
205
|
+
message('当前为目标引擎预览。可旋转、平移、缩放,查看后确认当前版本。');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const image = await verifiedImage(choice.file);
|
|
209
|
+
if (epoch !== cameraEpoch) { URL.revokeObjectURL(image.url); return; }
|
|
210
|
+
aspect = choice.camera.aspect_ratio;
|
|
211
|
+
if (Math.abs(image.width / image.height - aspect) > .001) { URL.revokeObjectURL(image.url); throw new Error('Blender 基准画幅与契约相机不一致'); }
|
|
212
|
+
nativeReady(image, 'Blender · 原生渲染');
|
|
213
|
+
view.setCamera(choice.camera); fit(); $('comparison').hidden = false;
|
|
214
|
+
await finishView();
|
|
215
|
+
$('loading').hidden = true; $('view-state').textContent = '对齐后的实际效果';
|
|
216
|
+
$('camera-note').textContent = `${choice.label} · ${image.width} × ${image.height} 基准画幅`;
|
|
217
|
+
message('拖动画面改变共享机位;停止操作后 Blender 更新。拖动分隔线对比两侧效果。');
|
|
218
|
+
} finally { busy = false; view?.setInteractive(true); $('camera').disabled = false; $('reload').disabled = false; $('target-engine').disabled = false; }
|
|
219
|
+
}
|
|
220
|
+
function failed(error) {
|
|
221
|
+
resetConfirmation();
|
|
222
|
+
$('comparison').hidden = true; $('loading').hidden = false; $('loading').classList.add('error'); $('view-state').textContent = '对比未就绪';
|
|
223
|
+
for (const kind of ['spatial', 'structural']) if ($(`${kind}-state`).textContent === '检查中') checkState(kind, { state: 'warn', label: '未完成', detail: '本次导入未完成,尚不能判断契约是否满足。' });
|
|
224
|
+
checkState('appearance', { state: 'error', label: '无法对比', detail: '两侧需要同一场景、同一机位的有效结果。' });
|
|
225
|
+
message(error.message); console.error(error);
|
|
226
|
+
}
|
|
227
|
+
async function load() {
|
|
228
|
+
if (busy) return;
|
|
229
|
+
if (!assetId) { failed(new Error('当前任务未指定场景。请由建模任务传入 asset_id 后打开视窗。')); return; }
|
|
230
|
+
busy = true; $('reload').disabled = true; $('camera').disabled = true; $('target-engine').disabled = true; $('comparison').hidden = true; $('loading').hidden = false; $('loading').classList.remove('error');
|
|
231
|
+
$('engine-name').textContent = engineName(); $('godot-image').hidden = true; $('godot-pending').hidden = targetEngine !== 'godot';
|
|
232
|
+
document.title = `场景对齐 · Blender / ${engineName()}`;
|
|
233
|
+
document.querySelector('.viewer').setAttribute('aria-label', `当前场景的 Blender 与 ${engineName()} 对比`);
|
|
234
|
+
$('divider').setAttribute('aria-label', `Blender 与 ${engineName()} 对比滑条`);
|
|
235
|
+
$('engine-view').style.visibility = targetEngine === 'godot' ? 'hidden' : '';
|
|
236
|
+
$('engine-render-label').textContent = targetEngine === 'godot' ? '等待原生渲染' : '当前引擎渲染';
|
|
237
|
+
invalidatePreview(); ++cameraEpoch; interacting = false; $('comparison').classList.remove('orbiting');
|
|
238
|
+
$('revision').textContent = '';
|
|
239
|
+
view?.dispose(); view = null; check = null; $('title').textContent = assetId === 'windworn_sanctuary' ? '风蚀圣域' : assetId; $('asset-id').textContent = assetId;
|
|
240
|
+
for (const kind of ['spatial', 'structural', 'appearance']) checkState(kind, { state: '', label: '检查中', detail: '读取当前资产契约…' });
|
|
241
|
+
message('检查当前场景的模型、资产契约与显示依赖…');
|
|
242
|
+
try {
|
|
243
|
+
const response = await fetch(`/api/packages/${encodeURIComponent(assetId)}/review`, { method: 'POST' });
|
|
244
|
+
const result = await response.json(); if (!response.ok || !result.ok) throw new Error(result.error || '场景检查失败'); check = result;
|
|
245
|
+
if (!check.technical_ok) throw new Error(`模型格式存在 ${check.report.issues.numErrors} 个错误,请在建模任务中修复后重新导入。`);
|
|
246
|
+
const contract = check.contract;
|
|
247
|
+
const reference = contract?.source_of_truth?.reference_camera || 'reference';
|
|
248
|
+
cameraChoices = (contract?.cameras || []).map(camera => ({ camera, label: camera.label || (camera.id === reference ? '参考机位' : camera.id === 'overview' ? '全景机位' : camera.id),
|
|
249
|
+
file: camera.reference_render || (camera.id === reference ? contract.source_of_truth?.reference_render || 'preview/blender_reference.png' : camera.id === 'overview' ? 'preview/blender_overview.png' : null) })).filter(c => c.file && check.files[c.file]);
|
|
250
|
+
hasReference = cameraChoices.length > 0;
|
|
251
|
+
if (!hasReference) cameraChoices = [{camera:null,label:'模型预览',file:null}];
|
|
252
|
+
$('divider').hidden = !hasReference;
|
|
253
|
+
$('blender-view').hidden = !hasReference;
|
|
254
|
+
document.querySelector('.frame-note').textContent = hasReference ? '同机位 · 同画幅' : '目标引擎预览';
|
|
255
|
+
$('camera').replaceChildren(...cameraChoices.map((choice, i) => { const o = document.createElement('option'); o.value = String(i); o.textContent = choice.label; return o; }));
|
|
256
|
+
const free = document.createElement('option'); free.value = 'free'; free.textContent = '自由视角'; free.disabled = true; $('camera').append(free);
|
|
257
|
+
aspect = cameraChoices[0].camera?.aspect_ratio || 1.6; fit();
|
|
258
|
+
$('comparison').hidden = false; $('comparison').style.visibility = 'hidden';
|
|
259
|
+
view = await createReviewView({ container: $('engine-view'), assetBase: `${apiBase}/files/`, asset: check.asset, contract, files: check.files, profile: check.profiles?.three_js,
|
|
260
|
+
interactive: false, controlElement: $('comparison'), onCameraChange: cameraChanged,
|
|
261
|
+
onCameraStart: () => { interacting = true; $('comparison').classList.add('orbiting'); clearTimeout(renderTimer); },
|
|
262
|
+
onCameraEnd: () => { interacting = false; $('comparison').classList.remove('orbiting'); schedulePreview(); }, onProgress: message });
|
|
263
|
+
const recipe = { translation_metres: view.root.position.toArray(), rotation_quaternion_xyzw: view.root.quaternion.toArray(), scale_xyz: view.root.scale.toArray() };
|
|
264
|
+
checkState('spatial', compareSpatial(contract, view.stats, recipe));
|
|
265
|
+
checkState('structural', compareStructural(contract, view.stats));
|
|
266
|
+
renderAdaptation();
|
|
267
|
+
$('revision').textContent = `资产 ${check.files['model.glb'].sha256.slice(0, 10)}${contract ? ` · 契约 r${contract.contract_revision || 1}` : ''}`;
|
|
268
|
+
$('comparison').style.visibility = ''; split(50); await setCamera(0);
|
|
269
|
+
} catch (error) { failed(error); }
|
|
270
|
+
finally { busy = false; $('reload').disabled = false; $('target-engine').disabled = false; }
|
|
271
|
+
}
|
|
272
|
+
$('reload').onclick = load;
|
|
273
|
+
$('target-engine').onchange = () => { targetEngine = $('target-engine').value; invalidatePreview(); if (!busy) load(); };
|
|
274
|
+
$('confirm-asset').onclick = async () => {
|
|
275
|
+
if (!stageView || !viewedCapture || busy || interacting) return;
|
|
276
|
+
busy = true; $('confirm-asset').disabled = true; $('reload').disabled = true; $('camera').disabled = true; $('target-engine').disabled = true; view.setInteractive(false);
|
|
277
|
+
try {
|
|
278
|
+
const result = await post('confirmation', { accepted: true, revision: check.revision, stage_view_token: stageView.token, ...viewedCapture });
|
|
279
|
+
check.revision = result.revision;
|
|
280
|
+
$('confirmation-state').textContent = `${engineName()} 当前版本已确认,可以导入`;
|
|
281
|
+
document.querySelector('.confirmation-bar').classList.add('confirmed');
|
|
282
|
+
message('确认已保存。可回到原任务继续导入;修改模型、材质、灯光或显示配置后,必须回 Stage 重新确认。');
|
|
283
|
+
stageView = null; viewedCapture = null;
|
|
284
|
+
} catch (error) { resetConfirmation(); message(error.message); }
|
|
285
|
+
finally { busy = false; $('reload').disabled = false; $('camera').disabled = false; $('target-engine').disabled = false; view.setInteractive(true); }
|
|
286
|
+
};
|
|
287
|
+
$('camera').onchange = () => setCamera(Number($('camera').value)).catch(failed);
|
|
288
|
+
window.addEventListener('pagehide', () => { ++cameraEpoch; invalidatePreview(); resizeObserver.disconnect(); view?.dispose(); if (referenceUrl) URL.revokeObjectURL(referenceUrl); });
|
|
289
|
+
load();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Viewer UI. Scene appearance is shared with synchronized game projects. */
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
4
|
+
import { loadCanonicalScene, applySceneCamera } from './scene-runtime.js';
|
|
5
|
+
|
|
6
|
+
export async function createReviewView({ container, assetBase, asset, contract = null, files, presentation, profile = null,
|
|
7
|
+
interactive = true, controlElement, onCameraChange = () => {}, onCameraStart = () => {}, onCameraEnd = () => {}, onProgress = () => {} }) {
|
|
8
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
|
9
|
+
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.5));
|
|
10
|
+
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
11
|
+
renderer.toneMapping = THREE.AgXToneMapping;
|
|
12
|
+
renderer.toneMappingExposure = 1;
|
|
13
|
+
const frame = document.createElement('div');
|
|
14
|
+
frame.className = 'render-frame'; frame.append(renderer.domElement); container.replaceChildren(frame);
|
|
15
|
+
const world = new THREE.Scene(), camera = new THREE.PerspectiveCamera(40, 1.6, .01, 2000);
|
|
16
|
+
const controls = new OrbitControls(camera, controlElement || renderer.domElement);
|
|
17
|
+
controls.enableDamping = false; controls.enabled = interactive;
|
|
18
|
+
let scene, observer, disposed = false;
|
|
19
|
+
try {
|
|
20
|
+
scene = await loadCanonicalScene({ renderer, world, assetBase, asset, contract, files, presentation, profile, onProgress });
|
|
21
|
+
const { root, stats, inventory, mode } = scene;
|
|
22
|
+
const center = new THREE.Box3(new THREE.Vector3().fromArray(stats.bounds.min), new THREE.Vector3().fromArray(stats.bounds.max)).getCenter(new THREE.Vector3());
|
|
23
|
+
const radius = Math.max(...stats.size, .1);
|
|
24
|
+
controls.target.copy(center); camera.position.copy(center).add(new THREE.Vector3(.9, .55, 1.3).multiplyScalar(radius)); camera.lookAt(center);
|
|
25
|
+
camera.near = Math.max(.001, radius / 2000); camera.far = Math.max(100, radius * 20);
|
|
26
|
+
let aspect = presentation?.camera.aspect || contract?.cameras?.[0]?.aspect_ratio || 1.6;
|
|
27
|
+
function render() { if (!disposed) scene.render(camera); }
|
|
28
|
+
function setCamera(value) {
|
|
29
|
+
if (!value) return;
|
|
30
|
+
applySceneCamera(camera, value); controls.target.fromArray(value.target || value.look_at); aspect = camera.aspect; render();
|
|
31
|
+
}
|
|
32
|
+
function getCamera() { return { position: camera.position.toArray(), quaternion: camera.quaternion.toArray(), target: controls.target.toArray(), fov: camera.fov, aspect: camera.aspect, near: camera.near, far: camera.far }; }
|
|
33
|
+
function resize() {
|
|
34
|
+
const width = Math.max(1, Math.min(container.clientWidth, container.clientHeight * aspect));
|
|
35
|
+
frame.style.width = `${width}px`; frame.style.height = `${width / aspect}px`;
|
|
36
|
+
renderer.setSize(width, width / aspect, true); camera.aspect = aspect; camera.updateProjectionMatrix(); render();
|
|
37
|
+
}
|
|
38
|
+
controls.addEventListener('change', () => { render(); onCameraChange(getCamera()); });
|
|
39
|
+
controls.addEventListener('start', onCameraStart); controls.addEventListener('end', onCameraEnd);
|
|
40
|
+
observer = new ResizeObserver(resize); observer.observe(container);
|
|
41
|
+
setCamera(presentation?.camera || contract?.cameras?.find(c => c.id === 'reference') || contract?.cameras?.[0]);
|
|
42
|
+
const initialCamera = getCamera(); resize(); onProgress('编译显示材质…'); await renderer.compileAsync(world, camera); render();
|
|
43
|
+
return {
|
|
44
|
+
root, world, camera, renderer, inventory, mode, stats, setCamera, getCamera, render,
|
|
45
|
+
setInteractive: enabled => { controls.enabled = enabled; }, resetCamera: () => setCamera(initialCamera),
|
|
46
|
+
diagnostic(kind) { scene.diagnostic(kind); render(); },
|
|
47
|
+
focus(index) {
|
|
48
|
+
const object = inventory[index]?.object; if (!object) return;
|
|
49
|
+
const box = new THREE.Box3().setFromObject(object, true), c = box.getCenter(new THREE.Vector3()), r = Math.max(box.getSize(new THREE.Vector3()).length(), 1);
|
|
50
|
+
controls.target.copy(c); camera.position.copy(c).add(new THREE.Vector3(1, .7, 1).multiplyScalar(r)); camera.lookAt(c); render();
|
|
51
|
+
},
|
|
52
|
+
capture() {
|
|
53
|
+
if (scene.diagnosticActive) throw new Error('请先切回材质画面再确认'); render();
|
|
54
|
+
return { screenshot: renderer.domElement.toDataURL('image/png'), presentation: { renderer: 'evomeme.review.v1', three_revision: THREE.REVISION, mode, camera: getCamera(),
|
|
55
|
+
recipe: { translation_metres: root.position.toArray(), rotation_quaternion_xyzw: root.quaternion.toArray(), scale_xyz: root.scale.toArray(), algorithm_version: 'evomeme.review.v1' } } };
|
|
56
|
+
}, dispose
|
|
57
|
+
};
|
|
58
|
+
} catch (error) { dispose(); throw error; }
|
|
59
|
+
function dispose() {
|
|
60
|
+
if (disposed) return; disposed = true;
|
|
61
|
+
observer?.disconnect(); controls.dispose(); scene?.dispose(); renderer.dispose(); frame.remove();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
:root{color-scheme:dark;--bg:#131819;--panel:#1a2022;--line:#303b3d;--ink:#e2e9e5;--muted:#94a39e;--mint:#b6dccc;--orange:#edb080}
|
|
2
|
+
.adaptation-panel{margin-top:14px;border:1px solid var(--line);border-radius:6px;background:var(--panel)}
|
|
3
|
+
.adaptation-panel summary{padding:15px 18px;cursor:pointer;font-weight:550;display:flex;gap:18px;justify-content:space-between;list-style:revert}
|
|
4
|
+
.adaptation-panel summary:focus-visible{outline:2px solid var(--mint);outline-offset:3px}
|
|
5
|
+
#adaptation-summary{font-size:11px;color:var(--orange);font-weight:400}
|
|
6
|
+
.adaptation-scope,.adaptation-footnote{padding:0 18px;font-size:11px;line-height:1.8;color:var(--muted)}
|
|
7
|
+
.adaptation-items{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1px;background:var(--line);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
|
|
8
|
+
.adaptation-row{padding:15px 18px;background:var(--panel);min-width:0}
|
|
9
|
+
.adaptation-title{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
|
|
10
|
+
.adaptation-title strong{font-size:12px;font-weight:550;overflow-wrap:anywhere}
|
|
11
|
+
.adaptation-badge{font-size:10px;white-space:nowrap;color:var(--mint)}
|
|
12
|
+
.adaptation-badge.missing{color:#ef9b8d}.adaptation-badge.approximate,.adaptation-badge.unverified{color:var(--orange)}
|
|
13
|
+
.adaptation-row p{font-size:11px;line-height:1.75;margin:9px 0 0;color:var(--ink)}
|
|
14
|
+
.adaptation-row .adaptation-action{color:var(--muted)}
|
|
15
|
+
.adaptation-buttons{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.adaptation-buttons button{font-size:11px;padding:6px 9px}
|
|
16
|
+
@media(max-width:800px){.adaptation-items{grid-template-columns:1fr}.adaptation-panel summary{align-items:flex-start;flex-direction:column;gap:7px}}
|
|
17
|
+
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:13px 'Segoe UI','Microsoft YaHei UI',sans-serif}button,select{font:inherit;color:inherit;background:var(--panel);border:1px solid #465250;border-radius:5px}button{padding:9px 14px;cursor:pointer}button:hover{border-color:var(--mint)}button:disabled,select:disabled{opacity:.45;cursor:default}button span{font-size:18px;margin-right:5px}select{padding:6px 29px 6px 10px;max-width:160px}button:focus-visible,select:focus-visible{outline:2px solid var(--mint);outline-offset:4px}[hidden]{display:none!important}
|
|
18
|
+
.comparison-app{width:min(1680px,100%);margin:auto;padding:25px 30px 12px}header{display:flex;align-items:center;gap:24px;padding-bottom:22px}.scene-title{flex:1;min-width:0}.eyebrow{margin:0 0 9px;font-size:10px;font-weight:600;letter-spacing:1.6px;color:var(--muted)}.eyebrow span{color:#586c63;padding:0 10px}h1{font-size:25px;letter-spacing:.3px;margin:0;font-weight:550}.asset-id{margin:7px 0 0;font-size:11px;color:var(--muted)}.task-context{display:flex;align-items:center;gap:8px;font-size:12px}.source-dot,.engine-dot{width:6px;height:6px;border-radius:50%;background:var(--orange)}.engine-dot{background:var(--mint)}.arrow{margin:0 8px;color:#73867e}
|
|
19
|
+
.viewer{border:1px solid var(--line);border-radius:7px;overflow:hidden;background:#0d1213}.view-toolbar{height:45px;display:flex;gap:12px;align-items:center;padding:0 17px;border-bottom:1px solid var(--line);background:var(--panel);font-size:11px;color:var(--muted)}.live-state{color:var(--ink);flex:1;display:flex;align-items:center;gap:8px}.live-state:before{content:'';display:inline-block;width:5px;height:5px;background:var(--mint);border-radius:50%}.frame-note{padding-left:14px;border-left:1px solid var(--line)}.stage{position:relative;height:clamp(290px,calc(100dvh - 335px),870px);min-height:290px;display:grid;place-items:center;overflow:hidden;background:#0c1112}.comparison{position:relative;overflow:hidden;isolation:isolate;touch-action:none;cursor:ew-resize;background:#0c1112}.engine-view{position:absolute;inset:0;display:grid;place-items:center;pointer-events:none}.render-frame canvas{display:block}.blender-view{position:absolute;inset:0;clip-path:inset(0 calc(100% - var(--split)) 0 0);pointer-events:none}.blender-view img{width:100%;height:100%;display:block;object-fit:fill;user-select:none}.source-label,.engine-label{position:absolute;top:15px;padding:9px 12px;border-radius:4px;display:flex;align-items:center;gap:9px;pointer-events:none;background:#13201be3;box-shadow:0 2px 12px #0002;backdrop-filter:blur(10px);font-size:10px}.source-label{left:15px}.engine-label{right:15px}.source-label b{color:#ffd4af}.engine-label b{color:#d4f5e6}.source-label b,.engine-label b{font-size:12px}.source-label span,.engine-label span{color:#c5d2cc}.divider{position:absolute;top:0;bottom:0;left:var(--split);width:38px;transform:translateX(-50%);cursor:ew-resize;touch-action:none;outline:none}.divider:before{content:'';position:absolute;left:18px;width:2px;height:100%;background:#eef6ec;box-shadow:0 0 8px #0007}.grip{position:absolute;top:50%;left:0;transform:translateY(-50%);width:38px;height:43px;border:1px solid #e8f2e3;border-radius:8px;background:#e8f2e3;box-shadow:0 4px 16px #0618105c;display:flex;align-items:center;justify-content:center;gap:6px;color:#30483c;font-size:23px;font-family:Georgia,serif}.grip i{height:15px;border-left:1px solid #b3c4b8}.divider:focus-visible .grip{outline:3px solid #efbc78;outline-offset:4px}.loading{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;color:var(--muted);padding:35px;text-align:center}.loading p{line-height:1.8;overflow-wrap:anywhere}.loader{width:22px;height:22px;border:1px solid #344a3f;border-top-color:var(--mint);border-radius:50%;animation:spin 1s linear infinite}.loading.error .loader{display:none}@keyframes spin{to{transform:rotate(360deg)}}
|
|
20
|
+
.view-caption{height:38px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 17px;background:var(--panel);font-size:10px;color:var(--muted)}.view-caption>span:first-child{display:flex;align-items:center;gap:8px}.split-symbol{display:inline-block;width:11px;height:11px;border:1px solid #78938a;background:linear-gradient(90deg,#a9cabb 50%,transparent 50%)}.contract-strip{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;margin-top:17px;border:1px solid var(--line);border-radius:6px;background:var(--panel)}article{padding:16px 18px;min-width:0}article+article{border-left:1px solid var(--line)}.check-heading{display:flex;align-items:center;gap:9px}.check-number{font-size:10px;color:#687e73;font-variant-numeric:tabular-nums}h2{font-size:13px;font-weight:500;margin:0;flex:1}h2 small{font-size:10px;margin-left:7px;color:var(--muted);font-weight:400}.check-state{font-size:10px;color:var(--muted);white-space:nowrap}.check-state.ok{color:var(--mint)}.check-state.warn{color:#e7c48c}.check-state.error{color:#eda69a}article p{font-size:11px;line-height:1.7;color:var(--muted);margin:11px 0 0}footer{display:flex;justify-content:space-between;gap:12px;align-items:baseline;color:#778a81;font-size:10px}footer p{line-height:1.6;overflow-wrap:anywhere}#revision{white-space:nowrap;font-variant-numeric:tabular-nums}
|
|
21
|
+
@media(min-height:1000px){.comparison-app{padding-top:32px}.stage{height:calc(100dvh - 355px)}}@media(max-width:800px){.comparison-app{padding:18px 16px 10px}.task-context{display:none}header{gap:10px}h1{font-size:22px}.frame-note{display:none}.source-label span,.engine-label span{display:none}.contract-strip{grid-template-columns:1fr}article{padding:12px 16px}article+article{border-left:0;border-top:1px solid var(--line)}article p{margin-top:6px}.view-caption{height:auto;min-height:36px;flex-wrap:wrap;padding:10px 12px}#camera-note{display:none}footer{align-items:flex-start}#revision{display:none}.stage{height:clamp(250px,52dvh,500px);min-height:250px}}
|
|
22
|
+
@media(prefers-reduced-motion:reduce){.loader{animation:none}}
|
|
23
|
+
.confirmation-bar{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:14px;padding:14px 18px;border:1px solid var(--line);border-radius:6px;background:var(--panel)}.confirmation-bar strong{font-size:12px;font-weight:550}.confirmation-bar p{color:var(--muted);font-size:11px;line-height:1.7;margin:6px 0 0}.confirmation-bar button{white-space:nowrap;background:#29473d;border-color:#608d78}.native-engine-image{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}.native-engine-pending{position:absolute;inset:0;background:#132021;display:grid;place-content:center;justify-items:center;gap:14px;color:var(--muted);pointer-events:none}.confirmation-bar.confirmed{border-color:#608d78}.stage{height:clamp(290px,calc(100dvh - 440px),870px)}@media(max-width:800px){.task-context{display:flex}.task-context>span,.task-context>label{display:none}.confirmation-bar{align-items:stretch;flex-direction:column;gap:10px}.stage{height:clamp(250px,48dvh,500px)}}
|
|
24
|
+
.comparison{cursor:grab}.comparison.orbiting{cursor:grabbing}.blender-pending{position:absolute;inset:0;background:radial-gradient(ellipse at 25% 50%,#22302e,#131c1d 75%)}.pending-content{position:absolute;left:calc(var(--split)/2);top:50%;transform:translate(-50%,-50%);width:220px;max-width:calc(var(--split) - 24px);display:flex;align-items:center;flex-direction:column;gap:14px;text-align:center;color:var(--muted);font-size:12px;line-height:1.7}.pending-content p{margin:0}.pending-content button{pointer-events:auto;font-size:11px;padding:7px 11px}.blender-pending.error .loader{display:none}.blender-view.pending img{visibility:hidden}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>场景对齐 · Blender / Three.js</title>
|
|
7
|
+
<link rel="stylesheet" href="./review.css">
|
|
8
|
+
<script type="importmap">{"imports":{"three":"./vendor/three170/build/three.module.js","three/addons/":"./vendor/three170/examples/jsm/"}}</script>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main class="comparison-app">
|
|
12
|
+
<header>
|
|
13
|
+
<div class="scene-title"><p class="eyebrow">SCENE ALIGNMENT <span>/</span> 场景对齐</p><h1 id="title">载入当前场景</h1><p id="asset-id" class="asset-id"></p></div>
|
|
14
|
+
<div class="task-context"><span class="source-dot"></span>Blender<span class="arrow">→</span><label for="target-engine">目标引擎</label><select id="target-engine"><option value="three_js">Three.js</option><option value="godot">Godot 4</option></select></div>
|
|
15
|
+
<button id="reload" disabled><span aria-hidden="true">↻</span> 重新载入</button>
|
|
16
|
+
</header>
|
|
17
|
+
<section class="viewer" aria-label="当前场景的 Blender 与 Three.js 对比">
|
|
18
|
+
<div class="view-toolbar"><span class="live-state" id="view-state">正在检查场景</span><label for="camera">对比机位</label><select id="camera" disabled><option>参考机位</option></select><span class="frame-note">同机位 · 同画幅</span></div>
|
|
19
|
+
<div id="stage" class="stage">
|
|
20
|
+
<div id="comparison" class="comparison" style="--split:50%" hidden>
|
|
21
|
+
<div id="engine-view" class="engine-view"></div><img id="godot-image" class="native-engine-image" alt="当前机位的 Godot 原生渲染" hidden><div id="godot-pending" class="native-engine-pending" hidden><span class="loader" aria-hidden="true"></span><p id="godot-progress">Godot 正在渲染…</p></div>
|
|
22
|
+
<div id="blender-view" class="blender-view"><img id="blender-image" alt="当前机位的 Blender 原生渲染" draggable="false"><div id="blender-pending" class="blender-pending" hidden><div class="pending-content"><span class="loader" aria-hidden="true"></span><p id="blender-progress" role="status">正在更新当前视角…</p><button id="retry-preview" hidden>重试当前视角</button></div></div></div>
|
|
23
|
+
<div class="source-label"><b>Blender</b><span id="blender-label">Cycles · 原生渲染</span></div>
|
|
24
|
+
<div class="engine-label"><b id="engine-name">Three.js</b><span id="engine-render-label">当前引擎渲染</span></div>
|
|
25
|
+
<div id="divider" class="divider" role="slider" tabindex="0" aria-label="Blender 与 Three.js 对比滑条" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50" aria-valuetext="左侧 Blender 50%,右侧 Three.js 50%" aria-orientation="horizontal"><span class="grip" aria-hidden="true">‹<i></i>›</span></div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="loading" id="loading"><span class="loader" aria-hidden="true"></span><p id="loading-text">读取当前任务的场景与资产契约…</p></div>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="view-caption"><span>左键旋转 · 右键平移 · 滚轮缩放 · 拖动分隔线对比</span><span id="camera-note">共享机位 · Blender 停止拖动后更新</span></div>
|
|
30
|
+
</section>
|
|
31
|
+
<details id="adaptation-panel" class="adaptation-panel" hidden>
|
|
32
|
+
<summary><span>效果转换与待核对项</span><span id="adaptation-summary"></span></summary>
|
|
33
|
+
<p id="adaptation-scope" class="adaptation-scope"></p>
|
|
34
|
+
<div id="adaptation-items" class="adaptation-items"></div>
|
|
35
|
+
<p class="adaptation-footnote">这些记录说明转换方式;是否接受当前视觉效果,由你查看两侧画面后决定。</p>
|
|
36
|
+
</details>
|
|
37
|
+
<section class="contract-strip" aria-label="资产契约对齐状态">
|
|
38
|
+
<article><div class="check-heading"><span class="check-number">01</span><h2>空间 <small>Spatial</small></h2><span id="spatial-state" class="check-state">检查中</span></div><p id="spatial-detail">位置、旋转、尺寸与原点</p></article>
|
|
39
|
+
<article><div class="check-heading"><span class="check-number">02</span><h2>结构 <small>Structural</small></h2><span id="structural-state" class="check-state">检查中</span></div><p id="structural-detail">场景节点、网格与材质关联</p></article>
|
|
40
|
+
<article><div class="check-heading"><span class="check-number">03</span><h2>视觉 <small>Appearance</small></h2><span id="appearance-state" class="check-state">检查中</span></div><p id="appearance-detail">材质与显示配方、实际渲染差异</p></article>
|
|
41
|
+
</section>
|
|
42
|
+
<section class="confirmation-bar" aria-label="导入前确认"><div><strong id="confirmation-state">导入前必须在 Stage 确认</strong><p id="confirmation-note">Blender 建模完成后,先查看空间、结构、材质和灯光效果,再确认当前版本。修改后需重新确认。</p></div><button id="confirm-asset" disabled>确认当前版本,允许导入</button></section>
|
|
43
|
+
<footer><p id="status" role="status" aria-live="polite">仅检查当前任务的场景。</p><span id="revision"></span></footer>
|
|
44
|
+
</main>
|
|
45
|
+
<script type="module" src="./review-app.js"></script>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|