feat(all): 迁移扩展相关功能

This commit is contained in:
plum
2026-04-08 15:34:43 +08:00
parent ad2dd979eb
commit d7c0dba569
278 changed files with 25207 additions and 62 deletions
@@ -1081,6 +1081,24 @@ export default {
// 其他
'Dragon':"龙",
},
"Expansion": "扩展",
expansion: {
"Heat map": "热力图",
"Path": "路径",
"Flat heatmap": "平面热力图",
"Elevation heatmap": "高程热力图",
"Flow path": "流动路径",
"Flowing light path": "流光路径",
"Tube path": "管道路径",
"UI Panel": "UI面板",
"Water": "水体",
"Circular Water Surface": "圆形水面",
"Cylinder Water Pool": "圆柱水池",
"Cylinder Water Pool Volume": "圆柱水池体",
"Square Water Surface": "方形水面",
"Square Water Pool": "方形水池",
"Square Water Pool Volume": "方形水池体",
},
"Light": '灯光',
"Camera": '相机',
}
@@ -0,0 +1,641 @@
<template>
<div id="extra-resource-expansion" class="flex flex-col">
<div class="mb-3 flex flex-wrap items-center gap-2">
<n-button
v-for="subCategory in subCategories"
:key="subCategory.key"
strong
secondary
round
:type="activeSubCategory === subCategory.key ? 'primary' : 'default'"
@click="selectSubCategory(subCategory.key)"
>
{{ subCategory.name }}
</n-button>
</div>
<div class="flex-1 overflow-y-auto">
<div class="grid grid-cols-[repeat(auto-fill,minmax(100px,1fr))] gap-3">
<n-card
size="small"
hoverable
v-for="item in filteredList"
:key="item.key"
@contextmenu.prevent="handlePreview(item)"
@dblclick="addToScene(item)"
draggable="true"
@dragstart="dragStart(item)"
@dragend="dragEnd"
>
<template #cover>
<img :src="item.image" :alt="item.key" draggable="false" class="w-full object-fill" />
</template>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger>{{ getItemName(item) }}</template>
<span>{{ getItemName(item) }}</span>
</n-tooltip>
</n-card>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, inject, computed, nextTick } from "vue";
import type { Ref } from "vue";
import { Box3, Vector3, type Object3D } from "three";
import { cpt } from "@/language";
import { useDragStore } from "@/store/modules/drag";
import { screenToWorld } from "@/utils/common/scenes";
import { App, AddObjectCommand, Heatmap, Path, UIPanel, Water, type Preview } from "@astral3d/engine";
interface ExpansionItem {
key: string;
image: string;
name: any;
options: Record<string, any>;
}
const searchText = inject("searchText") as Ref<string>;
const previewInfo = inject("previewInfo") as any;
const previewRef = inject("previewRef") as any;
const activeSubCategory = ref("heatmap");
const subCategories = ref([
{key: "heatmap", name: cpt("extra.resource.expansion.Heat map")},
{key: "path", name: cpt("extra.resource.expansion.Path")},
{key: "uipanel", name: cpt("extra.resource.expansion.UI Panel")},
{key: "water", name: cpt("extra.resource.expansion.Water")},
]);
const pathPreviewPoints = [
{x: -3.5, y: 0, z: -1.8},
{x: -1.8, y: 0, z: 1.2},
{x: 0.4, y: 0, z: -0.8},
{x: 2.4, y: 0, z: 1.6},
{x: 3.6, y: 0, z: -0.4},
];
const pathPreviewPoints3D = [
{x: -3.2, y: 0.2, z: -1.4},
{x: -1.6, y: 1.1, z: 1.0},
{x: 0.3, y: 0.4, z: -0.6},
{x: 2.1, y: 1.3, z: 1.4},
{x: 3.1, y: 0.5, z: -0.3},
];
const allList: Record<string, ExpansionItem[]> = {
heatmap: [
{
key: "flatHeatmap",
image: "/static/images/resource/expansion/heatmap/flatHeatmap.jpg",
name: cpt("extra.resource.expansion.Flat heatmap"),
options: {
mode: "flat",
heatmap: {
maxOpacity: 0.8,
minOpacity: 0,
},
data: {
max: 5,
min: 0,
points: [
{x: 0, y: 0, value: 5, radius: 1.6},
{x: 0.9, y: -0.5, value: 4.4, radius: 1.2},
{x: -0.8, y: 0.9, value: 4.0, radius: 1.1},
{x: 1.6, y: 0.8, value: 3.6, radius: 0.9},
{x: -1.7, y: -0.9, value: 3.2, radius: 0.9},
{x: 0.2, y: 1.8, value: 2.8, radius: 0.8},
{x: -0.4, y: -2.1, value: 4.6, radius: 0.8},
{x: 2.6, y: -1.9, value: 2.9, radius: 0.9},
{x: -2.4, y: 2.1, value: 3.7, radius: 0.9},
{x: 3.2, y: 2.9, value: 3.8, radius: 1.1},
{x: 4.1, y: 2.1, value: 3.0, radius: 0.9},
{x: 2.4, y: 3.9, value: 2.4, radius: 0.8},
{x: 4.5, y: 3.4, value: 3.8, radius: 0.7},
{x: -3.4, y: -2.7, value: 3.1, radius: 0.9},
{x: -4.2, y: -2.1, value: 2.3, radius: 0.8},
{x: -2.6, y: -3.9, value: 2.0, radius: 0.7},
{x: -4.6, y: -3.6, value: 1.4, radius: 0.6},
{x: -1.3, y: 3.1, value: 2.2, radius: 0.8},
{x: 1.5, y: 2.6, value: 2.7, radius: 0.8},
{x: 3.0, y: -3.7, value: 1.9, radius: 0.7},
{x: 4.2, y: -3.4, value: 4.4, radius: 0.6},
{x: -4.4, y: 1.4, value: 4.6, radius: 0.6},
{x: -3.7, y: 2.5, value: 2.1, radius: 0.7},
{x: -2.0, y: 4.4, value: 3.3, radius: 0.6},
{x: 0.0, y: 4.6, value: 1.2, radius: 0.6},
{x: 4.6, y: 0.2, value: 3.5, radius: 0.6},
{x: -4.8, y: 0.1, value: 4.0, radius: 0.5},
{x: 0.3, y: -4.5, value: 3.1, radius: 0.6},
{x: 1.8, y: -4.1, value: 1.4, radius: 0.6},
{x: -1.7, y: -4.2, value: 4.6, radius: 0.6},
{x: 3.6, y: -1.2, value: 2.5, radius: 0.8},
{x: -3.2, y: 0.6, value: 2.7, radius: 0.8},
{x: 0.0, y: -2.6, value: 4.3, radius: 0.8},
{x: -0.6, y: 2.2, value: 2.9, radius: 0.8},
{x: 2.4, y: 0.4, value: 3.1, radius: 0.9},
{x: -2.8, y: -0.6, value: 2.4, radius: 0.8},
{x: 1.0, y: 4.2, value: 4.6, radius: 0.6},
{x: -4.1, y: 4.1, value: 5.0, radius: 0.5},
],
},
},
},
{
key: "heightHeatmap",
image: "/static/images/resource/expansion/heatmap/heightHeatmap.jpg",
name: cpt("extra.resource.expansion.Elevation heatmap"),
options: {
mode: "height",
data: {
max: 5,
min: 0,
points: [
{x: 0, y: 0, value: 5, radius: 1.6},
{x: 0.9, y: -0.5, value: 4.4, radius: 1.2},
{x: -0.8, y: 0.9, value: 4.0, radius: 1.1},
{x: 1.6, y: 0.8, value: 3.6, radius: 0.9},
{x: -1.7, y: -0.9, value: 3.2, radius: 0.9},
{x: 0.2, y: 1.8, value: 2.8, radius: 0.8},
{x: -0.4, y: -2.1, value: 4.6, radius: 0.8},
{x: 2.6, y: -1.9, value: 2.9, radius: 0.9},
{x: -2.4, y: 2.1, value: 3.7, radius: 0.9},
{x: 3.2, y: 2.9, value: 3.8, radius: 1.1},
{x: 4.1, y: 2.1, value: 3.0, radius: 0.9},
{x: 2.4, y: 3.9, value: 2.4, radius: 0.8},
{x: 4.5, y: 3.4, value: 3.8, radius: 0.7},
{x: -3.4, y: -2.7, value: 3.1, radius: 0.9},
{x: -4.2, y: -2.1, value: 2.3, radius: 0.8},
{x: -2.6, y: -3.9, value: 2.0, radius: 0.7},
{x: -4.6, y: -3.6, value: 1.4, radius: 0.6},
{x: -1.3, y: 3.1, value: 2.2, radius: 0.8},
{x: 1.5, y: 2.6, value: 2.7, radius: 0.8},
{x: 3.0, y: -3.7, value: 1.9, radius: 0.7},
{x: 4.2, y: -3.4, value: 4.4, radius: 0.6},
{x: -4.4, y: 1.4, value: 4.6, radius: 0.6},
{x: -3.7, y: 2.5, value: 2.1, radius: 0.7},
{x: -2.0, y: 4.4, value: 3.3, radius: 0.6},
{x: 0.0, y: 4.6, value: 1.2, radius: 0.6},
{x: 4.6, y: 0.2, value: 3.5, radius: 0.6},
{x: -4.8, y: 0.1, value: 4.0, radius: 0.5},
{x: 0.3, y: -4.5, value: 3.1, radius: 0.6},
{x: 1.8, y: -4.1, value: 1.4, radius: 0.6},
{x: -1.7, y: -4.2, value: 4.6, radius: 0.6},
{x: 3.6, y: -1.2, value: 2.5, radius: 0.8},
{x: -3.2, y: 0.6, value: 2.7, radius: 0.8},
{x: 0.0, y: -2.6, value: 4.3, radius: 0.8},
{x: -0.6, y: 2.2, value: 2.9, radius: 0.8},
{x: 2.4, y: 0.4, value: 3.1, radius: 0.9},
{x: -2.8, y: -0.6, value: 2.4, radius: 0.8},
{x: 1.0, y: 4.2, value: 4.6, radius: 0.6},
{x: -4.1, y: 4.1, value: 5.0, radius: 0.5},
],
},
},
},
],
path: [
{
key: "flowPath",
image: "/static/images/resource/expansion/path/flow.png",
name: cpt("extra.resource.expansion.Flow path"),
options: {
mode: "path",
cornerRadius: 1,
cornerSplit: 30,
path: {
width: 1,
arrow: false,
progress: 1,
side: "both",
},
flow: {
enabled: true,
speed: 0.35,
direction: [1, 0],
},
material: {
map: "/static/images/resource/expansion/path/flow.png",
},
points: pathPreviewPoints,
},
},
{
key: "flowingLightPath",
image: "/static/images/resource/expansion/path/light.png",
name: cpt("extra.resource.expansion.Flowing light path"),
options: {
mode: "path",
cornerRadius: 1,
cornerSplit: 30,
path: {
width: 1,
arrow: false,
progress: 1,
side: "both",
},
flow: {
enabled: true,
speed: 0.5,
direction: [1, 0],
},
material: {
map: "/static/images/resource/expansion/path/light.png",
},
points: pathPreviewPoints,
},
},
{
key: "tubePath",
image: "/static/images/resource/expansion/path/diffuse.jpg",
name: cpt("extra.resource.expansion.Tube path"),
options: {
mode: "tube",
cornerRadius: 0.15,
cornerSplit: 6,
tube: {
radius: 0.18,
radialSegments: 12,
progress: 1,
startRad: 0,
},
material: {
color: "#4c90f5",
map: "/static/images/resource/expansion/path/diffuse.jpg",
transparent: true,
repeat: [1, 1],
},
points: pathPreviewPoints3D,
},
},
],
uipanel: [
{
key: "uiPanel",
image: "/static/images/resource/expansion/uipanel/uipanel.svg",
name: cpt("extra.resource.expansion.UI Panel"),
options: {
name: "UIPanel",
type: "block",
props: {
width: 1.6,
height: 0.9,
padding: 0.04,
backgroundColor: "#151515",
backgroundOpacity: 0.9,
borderRadius: 0.05,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
},
children: [
{
type: "block",
name: "Header",
props: {
width: 1.5,
height: 0.18,
backgroundColor: "#00b6a4",
backgroundOpacity: 0.95,
borderRadius: 0.04,
justifyContent: "center",
alignItems: "center",
},
children: [
{
type: "text",
name: "HeaderTitle",
props: {
textContent: "UIPanel",
fontSize: 0.07,
color: "#ffffff",
textAlign: "center",
},
},
],
},
{
type: "block",
name: "Body",
props: {
width: 1.5,
height: 0.5,
padding: 0.03,
backgroundColor: "#007e82",
backgroundOpacity: 0.9,
borderRadius: 0.04,
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
},
children: [
{
type: "text",
name: "BodyTitle",
props: {
textContent: "WYSIWYG",
fontSize: 0.06,
color: "#cfd8dc",
textAlign: "center",
},
},
{
type: "text",
name: "BodySubTitle",
props: {
textContent: "Drag nodes to layout",
fontSize: 0.045,
color: "#9aa4ad",
textAlign: "center",
},
},
],
},
{
type: "block",
name: "Footer",
props: {
width: 1.5,
height: 0.12,
backgroundColor: "#101010",
backgroundOpacity: 0.9,
borderRadius: 0.04,
justifyContent: "center",
alignItems: "center",
},
children: [
{
type: "text",
name: "FooterText",
props: {
textContent: "Astral3D UI",
fontSize: 0.04,
color: "#7bd5cd",
textAlign: "center",
},
},
],
},
],
},
},
],
water: [
{
key: "circularWaterSurface",
image: "/static/images/resource/expansion/water/circularWaterSurface.jpg",
name: cpt("extra.resource.expansion.Circular Water Surface"),
options: {
sky: window.viewer?.scene.environment,
name: "CircularWaterSurface",
type: "cylinder",
light: [0.7, 1, -0.3],
diameter: 5,
height: 5,
wallMode: "none",
wallOpacity: 0,
useSceneRefraction: 1,
surfaceTransmittance: 0.6,
normalStrength: 0.5,
refractionStrength: 0.035,
},
},
{
key: "cylinderWaterPool",
image: "/static/images/resource/expansion/water/cylinderWaterPool.jpg",
name: cpt("extra.resource.expansion.Cylinder Water Pool"),
options: {
sky: window.viewer?.scene.environment,
name: "CylinderWaterPool",
type: "cylinder",
light: [0.7, 1, -0.3],
diameter: 5,
height: 5,
wallMode: "wall",
wallOpacity: 1,
useSceneRefraction: 1,
surfaceTransmittance: 0.6,
normalStrength: 0.5,
refractionStrength: 0.035,
},
},
{
key: "cylinderWaterPoolVolume",
image: "/static/images/resource/expansion/water/cylinderWaterPoolVolume.jpg",
name: cpt("extra.resource.expansion.Cylinder Water Pool Volume"),
options: {
sky: window.viewer?.scene.environment,
name: "CylinderWaterPoolVolume",
type: "cylinder",
light: [0.7, 1, -0.3],
diameter: 5,
height: 5,
wallMode: "volume",
wallOpacity: 1,
useSceneRefraction: 1,
surfaceTransmittance: 0.6,
normalStrength: 0.5,
refractionStrength: 0.035,
},
},
{
key: "squareWaterSurface",
image: "/static/images/resource/expansion/water/squareWaterSurface.jpg",
name: cpt("extra.resource.expansion.Square Water Surface"),
options: {
sky: window.viewer?.scene.environment,
name: "SquareWaterSurface",
type: "square",
light: [0.7, 1, -0.3],
diameter: 5,
height: 5,
wallMode: "none",
wallOpacity: 0,
useSceneRefraction: 1,
surfaceTransmittance: 0.6,
normalStrength: 0.5,
refractionStrength: 0.035,
},
},
{
key: "squareWaterPool",
image: "/static/images/resource/expansion/water/squareWaterPool.jpg",
name: cpt("extra.resource.expansion.Square Water Pool"),
options: {
sky: window.viewer?.scene.environment,
name: "SquareWaterPool",
type: "square",
light: [0.7, 1, -0.3],
diameter: 5,
height: 5,
wallMode: "wall",
wallOpacity: 1,
useSceneRefraction: 1,
surfaceTransmittance: 0.6,
normalStrength: 0.5,
refractionStrength: 0.035,
},
},
{
key: "squareWaterPoolVolume",
image: "/static/images/resource/expansion/water/squareWaterPoolVolume.jpg",
name: cpt("extra.resource.expansion.Square Water Pool Volume"),
options: {
sky: window.viewer?.scene.environment,
name: "SquareWaterPoolVolume",
type: "square",
light: [0.7, 1, -0.3],
diameter: 5,
height: 5,
wallMode: "volume",
wallOpacity: 1,
useSceneRefraction: 1,
surfaceTransmittance: 0.6,
normalStrength: 0.5,
refractionStrength: 0.035,
},
},
],
};
const dragStore = useDragStore();
function getItemName(item: ExpansionItem): string {
return item.name?.value || item.name || "";
}
const filteredList = computed(() => {
const key = activeSubCategory.value;
const list = allList[key] || [];
return list.filter(item => getItemName(item).toLowerCase().includes(searchText.value.toLowerCase()));
});
function selectSubCategory(key: string) {
activeSubCategory.value = key;
}
function getDefaultAddPosition(): number[] | undefined {
const container = window.viewer?.container;
if (!container) return undefined;
const centerX = container.offsetWidth / 2;
const centerY = container.offsetHeight / 2;
const centerPosition = screenToWorld(centerX, centerY);
return centerPosition.toArray();
}
async function handlePreview(item: ExpansionItem) {
previewInfo.name = getItemName(item);
previewInfo.visible = true;
await nextTick();
previewRef.value?.getPreviewer().then((previewer: Preview) => {
if (!previewer) return;
let object3d: Object3D | null = null;
switch (activeSubCategory.value) {
case "heatmap":
object3d = new Heatmap(item.options);
break;
case "path":
object3d = new Path(item.options);
break;
case "uipanel":
object3d = new UIPanel(item.options);
break;
}
if (!object3d) return;
previewer.scene.add(object3d);
const box3 = new Box3();
if (activeSubCategory.value === "heatmap") {
box3.set(new Vector3(-5, 0, -5), new Vector3(5, 5, 5));
} else {
box3.setFromObject(object3d);
}
previewer.modules.controls.fitToBox(box3, true);
});
}
function addToScene(item: ExpansionItem, position?: number[]) {
const options = JSON.parse(JSON.stringify(item.options || {}));
if (!options.name) {
options.name = getItemName(item);
}
const initPosition = position || getDefaultAddPosition();
if (initPosition) {
options.position = initPosition;
}
switch (activeSubCategory.value) {
case "heatmap": {
const heatmap = new Heatmap(options);
App.execute(new AddObjectCommand(heatmap), `Add Heatmap: ${options.name}`);
break;
}
case "path": {
const path = new Path(options);
App.execute(new AddObjectCommand(path), `Add Path: ${options.name}`);
break;
}
case "uipanel": {
const uipanel = new UIPanel(options);
App.execute(new AddObjectCommand(uipanel), `Add UIPanel: ${options.name}`);
break;
}
case "water": {
Water.waterPool(options, options.position);
break;
}
}
}
function dragStart(item: ExpansionItem) {
dragStore.setData(item);
}
function dragEnd() {
if (dragStore.getActionTarget !== "addToScene" || dragStore.endArea !== "Scene") return;
const position = screenToWorld(dragStore.endPosition.x, dragStore.endPosition.y).toArray();
addToScene(dragStore.getData, position);
dragStore.setActionTarget("");
}
</script>
<style scoped lang="less">
#extra-resource-expansion {
overflow-x: hidden;
.n-card {
cursor: pointer;
:deep(.n-card-cover) {
display: flex;
justify-content: center;
align-items: center;
img {
aspect-ratio: 1/1;
}
}
:deep(.n-card__content) {
padding: 0.3rem 0;
font-size: 0.65rem;
text-align: center;
.n-button {
font-size: 0.65rem !important;
}
}
}
}
</style>
@@ -29,7 +29,7 @@
<script setup lang="ts">
import {computed, markRaw, reactive, ref, provide, useTemplateRef} from "vue";
import {cpt} from "@/language";
import {Buoy, CameraAction, HeatMap, LocationCompany, LocationHeart,ChoroplethMap, ModelAlt, Opacity} from "@vicons/carbon";
import {Buoy, CameraAction, HeatMap, LocationCompany, LocationHeart,ChoroplethMap, EdtLoop, ModelAlt, Opacity} from "@vicons/carbon";
import CommonPreview from "@/components/preview/CommonPreview.vue";
import Models from "@/views/editor/components/extraPane/resource/builtin/Models.vue";
import Materials from "@/views/editor/components/extraPane/resource/builtin/Materials.vue";
@@ -37,6 +37,7 @@ import Particles from "@/views/editor/components/extraPane/resource/builtin/Part
import Billboard from "@/views/editor/components/extraPane/resource/builtin/Billboard.vue";
import HtmlPanel from "@/views/editor/components/extraPane/resource/builtin/HtmlPanel.vue";
import Tiles from "@/views/editor/components/extraPane/resource/builtin/Tiles.vue";
import Expansion from "@/views/editor/components/extraPane/resource/builtin/Expansion.vue";
import Lights from "@/views/editor/components/extraPane/resource/builtin/Lights.vue";
import Cameras from "@/views/editor/components/extraPane/resource/builtin/Cameras.vue";
@@ -48,6 +49,7 @@ const categories = ref([
{ key: 'billboard', name: cpt("home.assets.Billboard"), icon: markRaw(LocationHeart), component: markRaw(Billboard) },
{ key: 'htmlPanel', name: cpt("extra.resource.Html panel"), icon: markRaw(LocationCompany), component: markRaw(HtmlPanel) },
{ key: 'tiles', name: "3D Tiles", icon: markRaw(ChoroplethMap), component: markRaw(Tiles) },
{ key: 'expansion', name: cpt("extra.resource.Expansion"), icon: markRaw(EdtLoop), component: markRaw(Expansion) },
{ key: 'lights', name: cpt("extra.resource.Light"), icon: markRaw(Buoy), component: markRaw(Lights) },
{ key: 'cameras', name: cpt("extra.resource.Camera"), icon: markRaw(CameraAction), component: markRaw(Cameras) },
]);