-
Notifications
You must be signed in to change notification settings - Fork 659
chore: release v2.24.1 #2827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
chore: release v2.24.1 #2827
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,7 @@ | ||
| export { exportImage } from './export-image'; | ||
| export { threeAnimation } from './three-animation'; | ||
| export { threeBuildings } from './three-buildings'; | ||
| export { threeEarthquake } from './three-earthquake'; | ||
| export { threeGeometry } from './three-geometry'; | ||
| export { threeParticles } from './three-particles'; | ||
| export { threeShader } from './three-shader'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| import { ThreeLayer, ThreeRender } from '@antv/l7-three'; | ||
| import * as THREE from 'three'; | ||
| import type { TestCase } from '../../types'; | ||
| import { CaseScene } from '../../utils'; | ||
|
|
||
| /** | ||
| * Three.js 动画效果演示 | ||
| * 展示动态旋转、缩放和波动的 3D 对象 | ||
| */ | ||
| export const threeAnimation: TestCase = async (options) => { | ||
| const scene = await CaseScene({ | ||
| ...options, | ||
| // Three.js r163+ 需要 WebGL2,必须使用 'device' 渲染器 | ||
| renderer: 'device', | ||
| mapConfig: { | ||
| style: 'dark', | ||
| center: [116.4074, 39.9042], | ||
| zoom: 12, | ||
| pitch: 45, | ||
| rotation: 0, | ||
| }, | ||
| }); | ||
|
|
||
| scene.registerRenderService(ThreeRender); | ||
|
|
||
| const center = scene.getCenter(); | ||
| const animatedObjects: Array<{ | ||
| mesh: THREE.Mesh; | ||
| type: 'rotate' | 'pulse' | 'wave'; | ||
| speed: number; | ||
| initialScale: number; | ||
| }> = []; | ||
|
|
||
| const threeJSLayer = new ThreeLayer({ | ||
| enableMultiPassRenderer: false, | ||
| onAddMeshes: (threeScene, layer) => { | ||
| // 环境光照 | ||
| threeScene.add(new THREE.AmbientLight(0xffffff, 0.4)); | ||
|
|
||
| // 主光源 | ||
| const mainLight = new THREE.DirectionalLight(0xffffff, 0.8); | ||
| mainLight.position.set(50, 100, 50); | ||
| threeScene.add(mainLight); | ||
|
|
||
| // 彩色点光源 | ||
| const colors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff]; | ||
| colors.forEach((color, i) => { | ||
| const angle = (i / colors.length) * Math.PI * 2; | ||
| const radius = 0.05; | ||
| const light = new THREE.PointLight(color, 1, 20000); | ||
| layer.setObjectLngLat( | ||
| light, | ||
| [center.lng + Math.cos(angle) * radius, center.lat + Math.sin(angle) * radius], | ||
| 5000, | ||
| ); | ||
| threeScene.add(light); | ||
| }); | ||
|
|
||
| // 创建旋转的环 | ||
| for (let i = 0; i < 3; i++) { | ||
| const radius = 2000 + i * 1500; | ||
| const tube = 200; | ||
| const geometry = new THREE.TorusGeometry(radius, tube, 16, 100); | ||
| const material = new THREE.MeshPhongMaterial({ | ||
| color: [0x049ef4, 0x00ff88, 0xff6600][i], | ||
| emissive: [0x049ef4, 0x00ff88, 0xff6600][i], | ||
| emissiveIntensity: 0.3, | ||
| transparent: true, | ||
| opacity: 0.8, | ||
| }); | ||
| const torus = new THREE.Mesh(geometry, material); | ||
|
|
||
| // 不同的初始旋转 | ||
| torus.rotation.x = Math.PI / 2 + i * 0.3; | ||
| torus.rotation.y = i * 0.5; | ||
|
|
||
| layer.setObjectLngLat(torus, [center.lng, center.lat], 0); | ||
| threeScene.add(torus); | ||
|
|
||
| animatedObjects.push({ | ||
| mesh: torus, | ||
| type: 'rotate', | ||
| speed: 0.01 * (i + 1) * (i % 2 === 0 ? 1 : -1), | ||
| initialScale: 1, | ||
| }); | ||
| } | ||
|
|
||
| // 创建脉冲球体 | ||
| for (let i = 0; i < 5; i++) { | ||
| const angle = (i / 5) * Math.PI * 2; | ||
| const distance = 0.03; | ||
| const geometry = new THREE.SphereGeometry(500, 32, 32); | ||
| const material = new THREE.MeshPhongMaterial({ | ||
| color: 0xff3366, | ||
| emissive: 0xff3366, | ||
| emissiveIntensity: 0.4, | ||
| transparent: true, | ||
| opacity: 0.7, | ||
| }); | ||
| const sphere = new THREE.Mesh(geometry, material); | ||
|
|
||
| layer.setObjectLngLat( | ||
| sphere, | ||
| [center.lng + Math.cos(angle) * distance, center.lat + Math.sin(angle) * distance], | ||
| 1000, | ||
| ); | ||
| threeScene.add(sphere); | ||
|
|
||
| animatedObjects.push({ | ||
| mesh: sphere, | ||
| type: 'pulse', | ||
| speed: 0.02 + i * 0.005, | ||
| initialScale: 1, | ||
| }); | ||
| } | ||
|
|
||
| // 创建波动效果的地表 | ||
| const planeGeometry = new THREE.PlaneGeometry(20000, 20000, 32, 32); | ||
| const planeMaterial = new THREE.MeshPhongMaterial({ | ||
| color: 0x049ef4, | ||
| emissive: 0x0044aa, | ||
| emissiveIntensity: 0.2, | ||
| transparent: true, | ||
| opacity: 0.5, | ||
| wireframe: true, | ||
| side: THREE.DoubleSide, | ||
| }); | ||
| const plane = new THREE.Mesh(planeGeometry, planeMaterial); | ||
| plane.rotation.x = -Math.PI / 2; | ||
|
|
||
| // 保存原始顶点位置用于动画 | ||
| const positions = planeGeometry.attributes.position.array as Float32Array; | ||
| plane.userData.originalPositions = new Float32Array(positions); | ||
|
|
||
| layer.setObjectLngLat(plane, [center.lng, center.lat], -500); | ||
| threeScene.add(plane); | ||
|
|
||
| animatedObjects.push({ | ||
| mesh: plane, | ||
| type: 'wave', | ||
| speed: 0.02, | ||
| initialScale: 1, | ||
| }); | ||
|
|
||
| // 创建中心发光核心 | ||
| const coreGeometry = new THREE.IcosahedronGeometry(800, 2); | ||
| const coreMaterial = new THREE.MeshPhongMaterial({ | ||
| color: 0xffffff, | ||
| emissive: 0x049ef4, | ||
| emissiveIntensity: 0.8, | ||
| flatShading: true, | ||
| }); | ||
| const core = new THREE.Mesh(coreGeometry, coreMaterial); | ||
| layer.setObjectLngLat(core, [center.lng, center.lat], 3000); | ||
| threeScene.add(core); | ||
|
|
||
| animatedObjects.push({ | ||
| mesh: core, | ||
| type: 'rotate', | ||
| speed: 0.03, | ||
| initialScale: 1, | ||
| }); | ||
|
|
||
| // 动画更新 | ||
| const clock = new THREE.Clock(); | ||
|
|
||
| const animate = () => { | ||
| const time = clock.getElapsedTime(); | ||
|
|
||
| animatedObjects.forEach((obj) => { | ||
| if (obj.type === 'rotate') { | ||
| obj.mesh.rotation.z += obj.speed; | ||
| obj.mesh.rotation.x += obj.speed * 0.5; | ||
| } else if (obj.type === 'pulse') { | ||
| const scale = obj.initialScale + Math.sin(time * 3 + obj.speed * 100) * 0.3; | ||
| obj.mesh.scale.set(scale, scale, scale); | ||
| } else if (obj.type === 'wave') { | ||
| const positions = (obj.mesh.geometry as THREE.PlaneGeometry).attributes.position | ||
| .array as Float32Array; | ||
| const originals = obj.mesh.userData.originalPositions as Float32Array; | ||
|
|
||
| for (let i = 0; i < positions.length; i += 3) { | ||
| const x = originals[i]; | ||
| const y = originals[i + 1]; | ||
| positions[i + 2] = | ||
| Math.sin(x * 0.001 + time) * 500 + Math.cos(y * 0.001 + time) * 500; | ||
| } | ||
|
|
||
| (obj.mesh.geometry as THREE.PlaneGeometry).attributes.position.needsUpdate = true; | ||
| obj.mesh.geometry.computeVertexNormals(); | ||
| } | ||
| }); | ||
|
|
||
| requestAnimationFrame(animate); | ||
| }; | ||
|
|
||
| animate(); | ||
| }, | ||
| }).animate(true); | ||
|
|
||
| scene.addLayer(threeJSLayer); | ||
|
|
||
| return scene; | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| import { ThreeLayer, ThreeRender } from '@antv/l7-three'; | ||
| import * as THREE from 'three'; | ||
| import type { TestCase } from '../../types'; | ||
| import { CaseScene } from '../../utils'; | ||
|
|
||
| /** | ||
| * Three.js 3D 建筑物效果 | ||
| * 使用多边形数据创建立体建筑物 | ||
| */ | ||
| export const threeBuildings: TestCase = async (options) => { | ||
| const scene = await CaseScene({ | ||
| ...options, | ||
| // Three.js r163+ 需要 WebGL2,必须使用 'device' 渲染器 | ||
| renderer: 'device', | ||
| mapConfig: { | ||
| style: 'dark', | ||
| // 数据位于深圳区域 | ||
| center: [113.95, 22.535], | ||
| zoom: 12, | ||
| pitch: 60, | ||
| rotation: 0, | ||
| }, | ||
| }); | ||
|
|
||
| scene.registerRenderService(ThreeRender); | ||
|
|
||
| // 加载建筑物数据 | ||
| const data = await fetch( | ||
| 'https://gw.alipayobjects.com/os/basement_prod/972566c5-a2b9-4a7e-8da1-bae9d0eb0117.json', | ||
| ).then((res) => res.json()); | ||
|
|
||
| const threeJSLayer = new ThreeLayer({ | ||
| enableMultiPassRenderer: false, | ||
| onAddMeshes: (threeScene, layer) => { | ||
| // 环境光照 | ||
| threeScene.add(new THREE.AmbientLight(0xffffff, 0.3)); | ||
|
|
||
| // 方向光(模拟阳光) | ||
| const sunLight = new THREE.DirectionalLight(0xffffff, 0.8); | ||
| sunLight.position.set(100, 200, 100); | ||
| sunLight.castShadow = true; | ||
| threeScene.add(sunLight); | ||
|
|
||
| // 蓝色补光 | ||
| const fillLight = new THREE.DirectionalLight(0x049ef4, 0.3); | ||
| fillLight.position.set(-100, 50, -100); | ||
| threeScene.add(fillLight); | ||
|
|
||
| // 处理建筑物数据 | ||
| const features = data.features || []; | ||
|
|
||
| features.forEach((feature: any) => { | ||
| // h20 是活动密度值(0~12),applyObjectLngLat 的 altitude 单位为米 | ||
| // rawValue * 50 → 高度范围 50~600m,与城市建筑物尺度匹配 | ||
| const rawValue = feature.properties?.h20 || 1; | ||
| const height = Math.max(rawValue * 10, 10); | ||
| const geometryType = feature.geometry?.type; | ||
| // 兼容 Polygon 和 MultiPolygon | ||
| const polygons: number[][][][] = | ||
| geometryType === 'MultiPolygon' | ||
| ? feature.geometry.coordinates | ||
| : [feature.geometry.coordinates]; | ||
|
|
||
| polygons.forEach((polygonCoords: number[][][]) => { | ||
| const outerRing = polygonCoords[0]; | ||
| if (!outerRing || outerRing.length < 3) return; | ||
|
|
||
| const color = getBuildingColor(rawValue); | ||
| const buildingMesh = createBuildingBox(outerRing, height, color); | ||
| if (!buildingMesh) return; | ||
|
|
||
| const center = getRingCenter(outerRing); | ||
| if (!center) return; | ||
|
|
||
| // applyObjectLngLat 正确处理默认地图和高德地图的坐标换算 | ||
| // altitude = height/2 使建筑物底面贴地,顶面在 height 处 | ||
| layer.applyObjectLngLat(buildingMesh, center, height / 2); | ||
| threeScene.add(buildingMesh); | ||
| }); | ||
| }); | ||
| }, | ||
| }).animate(false); | ||
|
|
||
| scene.addLayer(threeJSLayer); | ||
|
|
||
| // 地图交互时触发重绘,避免持续动画循环导致卡顿 | ||
| scene.on('mapMove', () => scene.render()); | ||
| scene.on('zoomChange', () => scene.render()); | ||
| scene.on('rotateChange', () => scene.render()); | ||
| scene.on('pitchChange', () => scene.render()); | ||
|
|
||
| return scene; | ||
| }; | ||
|
|
||
| /** | ||
| * 从多边形外环顶点创建立柱(Mesh) | ||
| * - outerRing: 经纬度坐标点数组(单个环) | ||
| * - height: 柱子高度(米),将通过 applyObjectLngLat 的 altitude 参数正确映射 | ||
| */ | ||
| function createBuildingBox( | ||
| outerRing: number[][], | ||
| height: number, | ||
| color: number, | ||
| ): THREE.Mesh | null { | ||
| if (!outerRing || outerRing.length < 3) return null; | ||
|
|
||
| // 计算多边形边界框 | ||
| let minX = Infinity, | ||
| maxX = -Infinity; | ||
| let minY = Infinity, | ||
| maxY = -Infinity; | ||
| outerRing.forEach(([x, y]) => { | ||
| minX = Math.min(minX, x); | ||
| maxX = Math.max(maxX, x); | ||
| minY = Math.min(minY, y); | ||
| maxY = Math.max(maxY, y); | ||
| }); | ||
|
|
||
| // 将经纬度差值粗略转换为米(applyObjectLngLat 的本地坐标系单位是米) | ||
| const width = Math.max((maxX - minX) * 100000, 20); | ||
| const depth = Math.max((maxY - minY) * 100000, 20); | ||
|
|
||
| // BoxGeometry(width, height_local, depth):Three.js 默认 Y 轴为高度方向 | ||
| const geometry = new THREE.BoxGeometry(width, height, depth); | ||
| const material = new THREE.MeshPhongMaterial({ color, flatShading: false }); | ||
| const mesh = new THREE.Mesh(geometry, material); | ||
|
|
||
| // 绕 X 轴旋转 90°:将 Three.js 本地 Y 轴(高度)映射到地图的 Z 轴(altitude 方向) | ||
| // 与 CylinderGeometry 的处理方式一致 | ||
| mesh.rotation.x = Math.PI / 2; | ||
|
|
||
| return mesh; | ||
| } | ||
|
|
||
| /** | ||
| * 根据活动密度值返回颜色 | ||
| */ | ||
| function getBuildingColor(value: number): number { | ||
| if (value >= 10) return 0xff3366; | ||
| if (value >= 8) return 0xff6633; | ||
| if (value >= 6) return 0xffcc33; | ||
| if (value >= 4) return 0x33cc66; | ||
| if (value >= 2) return 0x3366ff; | ||
| return 0x444466; | ||
| } | ||
|
|
||
| /** | ||
| * 计算外环中心点 | ||
| */ | ||
| function getRingCenter(outerRing: number[][]): [number, number] | null { | ||
| if (!outerRing || outerRing.length === 0) return null; | ||
| let sumX = 0, | ||
| sumY = 0; | ||
| outerRing.forEach(([x, y]) => { | ||
| sumX += x; | ||
| sumY += y; | ||
| }); | ||
| return [sumX / outerRing.length, sumY / outerRing.length]; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
使用
requestAnimationFrame创建独立的动画循环可能会导致性能问题和潜在的内存泄漏(如果图层被移除,循环不会停止)。建议使用
layer.setUpdate()来与 L7 的渲染循环集成。当在图层上启用.animate(true)时,传递给setUpdate的回调函数将在每一帧执行。这可以确保动画与 L7 的渲染同步,并得到妥善管理。