一、定义基本场景类
- 定义场景
- 定义相机
import { ArcRotateCamera, Color4, CubeTexture, Engine, GlowLayer, KeyboardEventTypes, Scene, Vector3 } from '@babylonjs/core';
import { AdvancedDynamicTexture } from '@babylonjs/gui';
class SceneManager {
public engine: Engine;
public scene: Scene;
public camera: ArcRotateCamera;
public advanceTexture: AdvancedDynamicTexture;
public glowLayer: GlowLayer;
constructor(canvas: HTMLCanvasElement) {
this.engine = new Engine(canvas);
const { scene, camera } = this.BuildScene();
this.scene = scene;
this.camera = camera;
this.advanceTexture = AdvancedDynamicTexture.CreateFullscreenUI('ui');
this.glowLayer = this.SetGlowLayer();
this.setEnv();
this.Resize();
this.RenderLoop();
}
BuildScene(): { scene: Scene; camera: ArcRotateCamera } {
const scene = new Scene(this.engine);
scene.clearColor = new Color4(0, 0, 0, 0);
const camera = new ArcRotateCamera('camera', 1.57, 1.57, 5.5, Vector3.Zero());
camera.attachControl(this.engine.getRenderingCanvas(), true);
camera.minZ = 0;
camera.wheelPrecision = 20;
camera.fov = 0.7;
camera.lowerAlphaLimit = null;
camera.upperAlphaLimit = null;
camera.lowerBetaLimit = null;
camera.upperBetaLimit = null;
camera.inputs.removeByType('ArcRotateCameraMouseWheelInput');
return {
camera,
scene
};
}
SetGlowLayer(): GlowLayer {
const glowLayer = new GlowLayer('gl');
glowLayer.isEnabled = true;
glowLayer.intensity = 0.3;
return glowLayer;
}
RenderLoop() {
this.engine.runRenderLoop(() => {
this.scene.render();
});
}
Resize() {
if (window) {
window.addEventListener('resize', () => {
this.engine.resize();
});
}
}
// 切换调试面板
public async RegisterInspectorOnInput(scene: Scene) {
const isEnv = import.meta.env.MODE === 'development';
if (isEnv) {
await Promise.all([import('@babylonjs/core/Debug/debugLayer'), import('@babylonjs/inspector')]);
scene.debugLayer.show({ embedMode: true });
}
const toggleInspector = () => {
if (scene.debugLayer.isVisible()) scene.debugLayer.hide();
else scene.debugLayer.show();
};
scene.onKeyboardObservable.add((kbInfo) => {
if (kbInfo.type === KeyboardEventTypes.KEYDOWN && kbInfo.event.key === 'i') {
toggleInspector();
}
});
}
setEnv() {
const skybox = CubeTexture.CreateFromPrefilteredData('env/Earth_Skybox.env', this.scene);
this.scene.createDefaultSkybox(skybox, false, 30, 0.98);
const hdr = CubeTexture.CreateFromPrefilteredData('env/Earth_Env.env', this.scene);
this.scene.environmentTexture = hdr;
this.scene.autoClear = false; // Color buffer
this.scene.autoClearDepthAndStencil = false; // Depth and stencil, obviously
}
Dispose() {
this.scene.dispose();
this.engine.dispose();
}
}
export { SceneManager };
二、定义子类继承父类
- 继承父类
- 加载json文件
- 利用墨卡托投影算法创建地图轮廓
import * as d3 from 'd3';
import earcut from 'earcut';
1.加载json文件
LoadJson() {
return new Promise<JSONType>((resolve) => {
Tools.LoadFile('json/china.json', (response) => {
try {
const json = JSON.parse(response as string) as JSONType;
resolve(json);
} catch (error) {
console.error(`地图json加载失败:${error}`);
}
});
});
}
2.生成边线和轮廓
2.1定义地图中心和投影算法
const center: [number, number] = [108.55, 34.32];
export function projection(args: [number, number]) {
const result = d3.geoMercator().center(center).scale(5).translate([0, 0])(args) as [number, number];
return result;
}
2.2 封装创建多边形算法
// 创建多边形
export function CreatePolygon(path: Vector3[], scene: Scene) {
return MeshBuilder.ExtrudePolygon(
'polygon',
{
shape: path,
sideOrientation: Mesh.FRONTSIDE,
depth: 0.5,
// 这里设置会覆盖材质的颜色
faceColors: [new Color4(1, 0, 0, 1), new Color4(0, 1, 0, 0.1), new Color4(1, 1, 1, 1)],
faceUV: [new Vector4(0, 0, 1, 1), new Vector4(0, 0, 1, 1), new Vector4(0, 0, 1, 1)]
},
scene,
earcut
);
}
// 创建边界线
export function CreateBoundaryLine(path: Vector3[]) {
const line = MeshBuilder.CreateLines('line', { points: path.concat(path[0]), updatable: true });
line.color = Color3.Random();
return line;
}
2.3 通过json数据创建多边形
CreateMapFromGeoJSON(geoJsonData: JSONType) {
return new Promise<Boolean>((resolve) => {
const { features } = geoJsonData;
const _createPolygonsFromCoordinates = (coordinate: number[][][] | number[][], meshList: Mesh[], lineList: Mesh[]) => {
coordinate.forEach(() => {
//获取轮廓
const path = coordinate[0].map((coord: any) => {
const [x, y] = projection([coord[0], coord[1]]) as [number, number];
// 投影在xz平面
return new Vector3(x, 0, -y);
});
const polygon = CreatePolygon(path, this.scene);
meshList.push(polygon);
const line = CreateBoundaryLine(path);
lineList.push(line);
});
};
features.forEach((feature, groundIndex) => {
const { center, name, centroid } = feature.properties;
const { coordinates, type } = feature.geometry;
const meshList: Mesh[] = [];
const lineList: Mesh[] = [];
if (type === 'Polygon') _createPolygonsFromCoordinates(coordinates, meshList, lineList);
if (type === 'MultiPolygon') coordinates.forEach((item) => _createPolygonsFromCoordinates(item, meshList, lineList));
if (meshList.length > 0) {
const mergedMesh = Mesh.MergeMeshes(meshList, true, true, undefined, false, true);
if (mergedMesh) {
mergedMesh.name = name + groundIndex;
this.regionList.push({
name,
center,
centroid,
mesh: mergedMesh
});
}
}
});
resolve(true);
});
}
2.4 创建省份标签
//创建区域标签
export function CreateRegionLabel(regionList: TypeRegionList[], advanceTexture: AdvancedDynamicTexture) {
const _drawText = (name: string, mesh: Mesh) => {
const text = new TextBlock();
text.text = name;
text.fontSize = 12;
text.color = 'white';
advanceTexture.addControl(text);
text.linkWithMesh(mesh);
};
const _drawImage = (mesh: Mesh) => {
const image = new Image('point', 'textures/point.png');
image.width = '70px';
image.height = '70px';
advanceTexture.addControl(image);
image.linkWithMesh(mesh);
// image.linkOffsetX = -15;
// image.linkOffsetY = 0;
};
regionList.forEach((item) => {
const { mesh, name } = item;
_drawText(name, mesh);
_drawImage(mesh);
});
}
2.5 创建飞线
export function CreateFlyline(regionList: TypeRegionList[], scene: Scene) {
const flylineCenter = [116.41995, 40.18994] as [number, number];
const [x, y] = projection(flylineCenter);
const origin = new Vector3(x, 0, -y);
// 创建texture
const createLineTexture = (): Texture => {
const textureColors = new Uint8Array([255, 255, 255, 0, 0, 255]);
const texture = new RawTexture(
textureColors,
textureColors.length / 3,
1,
Engine.TEXTUREFORMAT_RGB,
scene,
false,
true,
Engine.TEXTURE_NEAREST_NEAREST
);
texture.wrapU = RawTexture.WRAP_ADDRESSMODE;
texture.name = 'blue-white-texture';
texture.uScale = 5;
return texture;
};
const texture = createLineTexture();
const createLinesInstance = (points: Vector3[]): GreasedLineBaseMesh => {
const line = CreateGreasedLine(
'line',
{
points,
updatable: true
},
{
width: 0.008,
colorMode: GreasedLineMeshColorMode.COLOR_MODE_MULTIPLY
},
scene
);
return line;
};
regionList.forEach((city) => {
const { centroid } = city;
if (centroid) {
const [x, y] = projection(centroid);
const targetVec = new Vector3(x, 0, -y);
CreateWave(new Vector3(targetVec.x, targetVec.y + 0.01, targetVec.z), scene);
const middle = origin.add(targetVec).scale(0.5);
let control = new Vector3(middle.x, 1, middle.z);
// 创建贝塞尔曲线
const curve = Curve3.CreateQuadraticBezier(origin, control, targetVec, 64);
// 将贝塞尔曲线的点赋予line
const line = createLinesInstance(curve.getPoints());
(line.material as StandardMaterial).emissiveTexture = texture;
texture.uScale = 5;
scene.onBeforeRenderObservable.add(() => {
texture.uOffset += -0.0005 * scene.getAnimationRatio();
});
}
});
}
2.6 创建缩放动画
export function CreateWave(position: Vector3, scene: Scene) {
const plane = MeshBuilder.CreatePlane('wave', { size: 0.15 });
const mat = new StandardMaterial('mat');
const texture = new Texture('textures/wave.png');
mat.emissiveColor = Color3.White();
mat.diffuseTexture = texture;
mat.useAlphaFromDiffuseTexture = true;
texture.hasAlpha = true;
plane.material = mat;
plane.position = position;
plane.rotation.x = Math.PI / 2;
let radio = 1.0; // 初始化缩放比例
let size = 1; // 初始大小
scene.onBeforeRenderObservable.add(() => {
radio += 0.01; // 控制缩放速度
const scling = size * radio;
const initVector = new Vector3(scling, scling, scling);
plane.scaling = initVector;
if (radio <= 1.5) {
plane.material!.alpha = (radio - 1) * 2; // 透明度从0到1
} else if (radio > 1.5 && radio <= 2) {
plane.material!.alpha = 1 - (radio - 1.5) * 2; // 透明度从1到0
} else {
radio = 1.0; // 重置缩放比例
}
});
}
2.7 创建action
export function AddEvent(regionList: TypeRegionList[]) {
regionList.forEach((item) => {
const { mesh, name } = item;
mesh.actionManager = new ActionManager();
// 改变样式action
mesh.actionManager.registerAction(new SetValueAction(ActionManager.OnPointerOverTrigger, mesh.material, 'diffuseColor', Color3.Blue()));
mesh.actionManager.registerAction(
new SetValueAction(ActionManager.OnPointerOutTrigger, mesh.material, 'diffuseColor', (mesh.material as StandardMaterial)!.diffuseColor)
);
mesh.actionManager.registerAction(new InterpolateValueAction(ActionManager.OnPointerOverTrigger, mesh, 'scaling', new Vector3(1, -1.2, 1), 300));
mesh.actionManager.registerAction(new InterpolateValueAction(ActionManager.OnPointerOutTrigger, mesh, 'scaling', new Vector3(1, 1, 1), 300));
// 点击事件action
mesh.actionManager.registerAction(
new ExecuteCodeAction(
{
trigger: ActionManager.OnLeftPickTrigger,
// 参数传递
parameter: function (actionEvent: any) {
return actionEvent.sourceEvent.key === 'R';
}
},
(evt) => {
console.log(evt, name);
// 派发事件
emitter.emit(EVENT_NAME.SET_PROVINCE_DATA, name as TProvinceKeys);
}
)
);
});
}
3. 构造函数中调用
constructor(params: { canvas: HTMLCanvasElement; topList: String[] }) {
const { canvas, topList } = params;
super(canvas);
// new HemisphericLight('ligt', Vector3.Up());
this.LoadJson().then((geoJson: JSONType) => {
this.CreateMapFromGeoJSON(geoJson).then(() => {
// 创建区域标签
CreateRegionLabel(this.regionList, this.advanceTexture);
// 创建飞线
CreateFlyline(this.regionList, this.scene);
// 添加交互事件
AddEvent(this.regionList);
// 轮播
this.SetupLoopInteraction();
});
});
}
4 效果展示