diff --git a/src/vue-amap-extra/packages/ThreeGltf/ThreeGltf.vue b/src/vue-amap-extra/packages/ThreeGltf/ThreeGltf.vue
index b416bbd190389550673ca41dccf138744d36ae67..16c76af7b9a3338b60fd7e39b30413a8567c3bff 100644
--- a/src/vue-amap-extra/packages/ThreeGltf/ThreeGltf.vue
+++ b/src/vue-amap-extra/packages/ThreeGltf/ThreeGltf.vue
@@ -1,84 +1,130 @@
-
+
diff --git a/src/vue-amap-extra/packages/ThreeLayer/CSS2DRenderer.js b/src/vue-amap-extra/packages/ThreeLayer/CSS2DRenderer.js
new file mode 100644
index 0000000000000000000000000000000000000000..4db6def74f4a870d0681e8f6a549867deae0e9b1
--- /dev/null
+++ b/src/vue-amap-extra/packages/ThreeLayer/CSS2DRenderer.js
@@ -0,0 +1,182 @@
+import { Matrix4, Object3D, Vector2, Vector3 } from 'three';
+
+class CSS2DObject extends Object3D {
+ constructor(element = document.createElement('div')) {
+ super();
+
+ this.isCSS2DObject = true;
+
+ this.element = element;
+
+ this.element.style.position = 'absolute';
+ this.element.style.userSelect = 'none';
+
+ this.element.setAttribute('draggable', false);
+
+ this.center = new Vector2(0.5, 0.5); // ( 0, 0 ) is the lower left; ( 1, 1 ) is the top right
+
+ this.addEventListener('removed', function () {
+ this.traverse(function (object) {
+ if (object.element instanceof Element && object.element.parentNode !== null) {
+ object.element.parentNode.removeChild(object.element);
+ }
+ });
+ });
+ }
+
+ copy(source, recursive) {
+ super.copy(source, recursive);
+
+ const self = this;
+
+ self.element = source.element.cloneNode(true);
+
+ self.center = source.center;
+
+ return self;
+ }
+}
+
+//
+
+const _vector = new Vector3();
+const _viewMatrix = new Matrix4();
+const _viewProjectionMatrix = new Matrix4();
+const _a = new Vector3();
+const _b = new Vector3();
+
+class CSS2DRenderer {
+ constructor(parameters = {}) {
+ const _this = this;
+
+ let _width, _height;
+ let _widthHalf, _heightHalf;
+
+ const cache = {
+ objects: new WeakMap(),
+ };
+
+ const domElement =
+ parameters.element !== undefined ? parameters.element : document.createElement('div');
+
+ domElement.style.overflow = 'hidden';
+
+ this.domElement = domElement;
+
+ this.getSize = function () {
+ return {
+ width: _width,
+ height: _height,
+ };
+ };
+
+ this.render = function (scene, camera) {
+ if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld();
+ if (camera.parent === null && camera.matrixWorldAutoUpdate === true)
+ camera.updateMatrixWorld();
+
+ _viewMatrix.copy(camera.matrixWorldInverse);
+ _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix);
+
+ renderObject(scene, scene, camera);
+ zOrder(scene);
+ };
+
+ this.setSize = function (width, height) {
+ _width = width;
+ _height = height;
+
+ _widthHalf = _width / 2;
+ _heightHalf = _height / 2;
+
+ domElement.style.width = width + 'px';
+ domElement.style.height = height + 'px';
+ };
+
+ function renderObject(object, scene, camera) {
+ if (object.isCSS2DObject) {
+ _vector.setFromMatrixPosition(object.matrixWorld);
+ _vector.applyMatrix4(_viewProjectionMatrix);
+
+ const visible =
+ object.visible === true &&
+ _vector.z >= -1 &&
+ _vector.z <= 1 &&
+ object.layers.test(camera.layers) === true;
+ object.element.style.display = visible === true ? '' : 'none';
+
+ if (visible === true) {
+ object.onBeforeRender(_this, scene, camera);
+
+ const element = object.element;
+
+ element.style.transform =
+ 'translate(' +
+ -100 * object.center.x +
+ '%,' +
+ -100 * object.center.y +
+ '%)' +
+ 'translate(' +
+ (_vector.x * _widthHalf + _widthHalf) +
+ 'px,' +
+ (-_vector.y * _heightHalf + _heightHalf) +
+ 'px)';
+
+ if (element.parentNode !== domElement) {
+ domElement.appendChild(element);
+ }
+
+ object.onAfterRender(_this, scene, camera);
+ }
+
+ const objectData = {
+ distanceToCameraSquared: getDistanceToSquared(camera, object),
+ };
+
+ cache.objects.set(object, objectData);
+ }
+
+ for (let i = 0, l = object.children.length; i < l; i++) {
+ renderObject(object.children[i], scene, camera);
+ }
+ }
+
+ function getDistanceToSquared(object1, object2) {
+ _a.setFromMatrixPosition(object1.matrixWorld);
+ _b.setFromMatrixPosition(object2.matrixWorld);
+
+ return _a.distanceToSquared(_b);
+ }
+
+ function filterAndFlatten(scene) {
+ const result = [];
+
+ scene.traverse(function (object) {
+ if (object.isCSS2DObject) result.push(object);
+ });
+
+ return result;
+ }
+
+ function zOrder(scene) {
+ const sorted = filterAndFlatten(scene).sort(function (a, b) {
+ if (a.renderOrder !== b.renderOrder) {
+ return b.renderOrder - a.renderOrder;
+ }
+
+ const distanceA = cache.objects.get(a).distanceToCameraSquared;
+ const distanceB = cache.objects.get(b).distanceToCameraSquared;
+
+ return distanceA - distanceB;
+ });
+
+ const zMax = sorted.length;
+
+ for (let i = 0, l = sorted.length; i < l; i++) {
+ sorted[i].element.style.zIndex = zMax - i;
+ }
+ }
+ }
+}
+
+export { CSS2DObject, CSS2DRenderer };
diff --git a/src/vue-amap-extra/packages/ThreeLayer/CustomThreeLayer.ts b/src/vue-amap-extra/packages/ThreeLayer/CustomThreeLayer.ts
index dbd71c63af28d90da521c6398ecb0ad7ecdfb0a6..ca87abc761e4f34ce31ac0df37f07f7eecde0876 100644
--- a/src/vue-amap-extra/packages/ThreeLayer/CustomThreeLayer.ts
+++ b/src/vue-amap-extra/packages/ThreeLayer/CustomThreeLayer.ts
@@ -11,102 +11,113 @@ import {
Vector2,
AxesHelper,
Raycaster,
- Clock
-} from 'three';
-import {merge, bind} from "lodash-es";
-import {HDRCubeTextureLoader} from "three/examples/jsm/loaders/HDRCubeTextureLoader.js";
-import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
-import {ThreeLayer} from '@vuemap/three-layer'
-import type { Texture, Camera,
- WebGLRenderer,
- Scene, Object3D
-} from 'three';
-import type {HDROptions, LightOption} from "./Type";
-import type {ThreeLayerOptions} from '@vuemap/three-layer'
+ Clock,
+} from "three";
+import { merge, bind } from "lodash-es";
+import { HDRCubeTextureLoader } from "three/examples/jsm/loaders/HDRCubeTextureLoader.js";
+import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
+import { ThreeLayer } from "@vuemap/three-layer";
+import type { Texture, Camera, WebGLRenderer, Scene, Object3D } from "three";
+import type { HDROptions, LightOption } from "./Type";
+import type { ThreeLayerOptions } from "@vuemap/three-layer";
+import { CSS2DRenderer } from "./CSS2DRenderer";
-interface Options extends ThreeLayerOptions{
- lights?: LightOption[] // 灯光数组
- hdr?: HDROptions // 开启HDR配置
- axesHelper: boolean // 是否开启箭头,用于debug,默认不开启
+interface Options extends ThreeLayerOptions {
+ lights?: LightOption[]; // 灯光数组
+ hdr?: HDROptions; // 开启HDR配置
+ axesHelper: boolean; // 是否开启箭头,用于debug,默认不开启
+ createCssRender?: boolean; //是否创建CssRender
}
-class CustomThreeLayer extends ThreeLayer{
-
+class CustomThreeLayer extends ThreeLayer {
lightTypes = {
AmbientLight, // 环境光 环境光会均匀的照亮场景中的所有物体
DirectionalLight, // 平行光 平行光是沿着特定方向发射的光
HemisphereLight, // 半球光 光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。
PointLight, // 点光源 从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光
RectAreaLight, // 平面光光源 平面光光源从一个矩形平面上均匀地发射光线。这种光源可以用来模拟像明亮的窗户或者条状灯光光源
- SpotLight // 聚光灯 光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大
- }
- raycaster: Raycaster | undefined // 射线,用于判断点击或者鼠标移动是否碰到物体
+ SpotLight, // 聚光灯 光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大
+ };
+ raycaster: Raycaster | undefined; // 射线,用于判断点击或者鼠标移动是否碰到物体
mouse: Vector2;
envMap: any; // HDR的环境贴图
clickFun: any;
hoverFun: any;
resizeFun: any;
effectComposer?: EffectComposer;
- renderPass: any
- passNum = 0
- passList = [] as any[]
- clock = new Clock()
+ renderPass: any;
+ passNum = 0;
+ passList = [] as any[];
+ clock = new Clock();
preHoverGroup = null as Object3D | null;
+ cssRenderer = null as CSS2DRenderer | null;
constructor(map: any, options: Options, callback: () => void) {
- options.onInit = (render,scene) => {
+ options.onInit = (render, scene) => {
this.raycaster = new Raycaster();
- if(options.axesHelper){
- const axesHelper = new AxesHelper( 10000 );
- scene.add( axesHelper );
+ if (options.axesHelper) {
+ const axesHelper = new AxesHelper(10000);
+ scene.add(axesHelper);
}
this.renderer?.setPixelRatio(window.devicePixelRatio);
+ if (options.createCssRender) this.creatCssRender(map);
this.createEffect();
this.createLights(options.lights || []);
this.createHDR(options.hdr);
this.bindEvents();
- if(callback){
- callback()
+ if (callback) {
+ callback();
}
- }
+ };
options.onRender = (render, scene, camera) => {
- if(this.passNum > 0){
- this.effectComposer?.render(this.clock.getDelta())
- }else{
+ if (this.passNum > 0) {
+ this.effectComposer?.render(this.clock.getDelta());
+ } else {
this.renderer?.render(this.scene as Scene, camera as Camera);
+ this.cssRenderer?.render(this.scene as Scene, camera as Camera);
}
- }
- super(map, options)
+ };
+ super(map, options);
this.mouse = new Vector2();
}
updateEffectComposerSize() {
- if(this.effectComposer && this.renderer){
- const size = this.renderer.getSize( new Vector2() );
- this.effectComposer.setSize( size.x , size.y);
+ if (this.effectComposer && this.renderer) {
+ const size = this.renderer.getSize(new Vector2());
+ this.effectComposer.setSize(size.x, size.y);
+ this.cssRenderer?.setSize(size.x, size.y);
}
}
createEffect() {
- const size = this.renderer?.getSize( new Vector2() );
- this.effectComposer = new EffectComposer( this.renderer as WebGLRenderer );
- this.effectComposer.setSize( size?.x as number, size?.y as number);
-
+ const size = this.renderer?.getSize(new Vector2());
+ this.effectComposer = new EffectComposer(this.renderer as WebGLRenderer);
+ this.effectComposer.setSize(size?.x as number, size?.y as number);
+ this.cssRenderer?.setSize(size?.x as number, size?.y as number);
// const renderPass = new ThreeRenderPass( this.scene, this.camera );
// this.renderPass = renderPass;
// this.effectComposer.addPass(renderPass);
}
- addPass(pass: any){
+ creatCssRender(map: any) {
+ const element = map.getContainer().querySelector(".amap-markers");
+ this.cssRenderer = new CSS2DRenderer();
+ this.cssRenderer.domElement.style.position = "absolute";
+ this.cssRenderer.domElement.style.left = "0";
+ this.cssRenderer.domElement.style.top = "0";
+ element.appendChild(this.cssRenderer.domElement);
+ }
+
+ addPass(pass: any) {
this.effectComposer?.addPass(pass);
this.passNum++;
this.passList.push(pass);
}
- removePass(pass: any){
- const index = this.passList.indexOf( pass );
- if ( index !== - 1 ) {
- this.passList.splice( index, 1 );
+ removePass(pass: any) {
+ const index = this.passList.indexOf(pass);
+ if (index !== -1) {
+ this.passList.splice(index, 1);
}
this.effectComposer?.removePass(pass);
this.passNum--;
@@ -114,14 +125,20 @@ class CustomThreeLayer extends ThreeLayer{
createLights(lights: LightOption[] | undefined) {
const defaultLightOptions = {
- type: 'DirectionalLight', // 灯光类型, 可选值见下面的字典
- args: [] // 灯光初始化时需要的参数,具体参数顺序可以查看threejs官网灯光的说明。 采用 ...args 的方式进行初始化
+ type: "DirectionalLight", // 灯光类型, 可选值见下面的字典
+ args: [], // 灯光初始化时需要的参数,具体参数顺序可以查看threejs官网灯光的说明。 采用 ...args 的方式进行初始化
};
if (lights && lights.length > 0) {
- lights.forEach(lightOptions => {
- lightOptions = merge({}, defaultLightOptions, lightOptions) as LightOption;
+ lights.forEach((lightOptions) => {
+ lightOptions = merge(
+ {},
+ defaultLightOptions,
+ lightOptions
+ ) as LightOption;
if (this.lightTypes[lightOptions.type]) {
- const light = new this.lightTypes[lightOptions.type](...lightOptions.args);
+ const light = new this.lightTypes[lightOptions.type](
+ ...lightOptions.args
+ );
const position = lightOptions.position;
const lookAt = lightOptions.lookAt;
if (position) {
@@ -132,7 +149,7 @@ class CustomThreeLayer extends ThreeLayer{
}
this.add(light);
} else {
- console.warn('当前设置的灯光类型不存在');
+ console.warn("当前设置的灯光类型不存在");
}
});
}
@@ -142,12 +159,16 @@ class CustomThreeLayer extends ThreeLayer{
if (!hdr) {
return;
}
- const options = merge({}, {
- urls: [], // HDR贴图下载地址,需要6个文件,代表6个方向
- path: '/', // HDR下载地址的路径前缀
- // roughness: 0.0,
- exposure: 1.0 // 光亮程度
- }, hdr);
+ const options = merge(
+ {},
+ {
+ urls: [], // HDR贴图下载地址,需要6个文件,代表6个方向
+ path: "/", // HDR下载地址的路径前缀
+ // roughness: 0.0,
+ exposure: 1.0, // 光亮程度
+ },
+ hdr
+ );
const render = this.renderer as WebGLRenderer;
render.physicallyCorrectLights = true;
render.outputEncoding = sRGBEncoding;
@@ -191,24 +212,24 @@ class CustomThreeLayer extends ThreeLayer{
this.clickFun = bind(this._clickEvent, this);
this.hoverFun = bind(this._hoverEvent, this);
this.resizeFun = bind(this.updateEffectComposerSize, this);
- if(this.canvas){
- this.canvas.addEventListener('click', this.clickFun, false);
- this.canvas.addEventListener('mousemove', this.hoverFun, false);
- }else{
- this.map.on('click', this.clickFun);
- this.map.on('mousemove', this.hoverFun);
+ if (this.canvas) {
+ this.canvas.addEventListener("click", this.clickFun, false);
+ this.canvas.addEventListener("mousemove", this.hoverFun, false);
+ } else {
+ this.map.on("click", this.clickFun);
+ this.map.on("mousemove", this.hoverFun);
}
- this.map.on('resize', this.resizeFun);
+ this.map.on("resize", this.resizeFun);
}
ubBindEvents() {
- this.map.off('click', this.clickFun);
- this.map.off('mousemove', this.hoverFun);
- this.map.on('off', this.resizeFun);
+ this.map.off("click", this.clickFun);
+ this.map.off("mousemove", this.hoverFun);
+ this.map.on("off", this.resizeFun);
}
_getOriginEvent(e: any) {
- if(e.originEvent){
+ if (e.originEvent) {
return e.originEvent;
}
return e;
@@ -218,10 +239,10 @@ class CustomThreeLayer extends ThreeLayer{
e = this._getOriginEvent(e);
const group = this._intersectGltf(e);
if (group) {
- if(group.userData.$vue){
- group.userData.$vue.$emit('click', group);
+ if (group.userData.$vue) {
+ group.userData.$vue.$emit("click", group);
}
- this.emit('click', group);
+ this.emit("click", group);
}
}
@@ -231,19 +252,22 @@ class CustomThreeLayer extends ThreeLayer{
if (group) {
if (!group.userData.isHover) {
group.userData.isHover = true;
- if(group.userData.$vue){
- group.userData.$vue.$emit('mouseover', group);
- }else{
- this.emit('mouseover', group);
+ if (group.userData.$vue) {
+ group.userData.$vue.$emit("mouseover", group);
+ } else {
+ this.emit("mouseover", group);
}
}
}
- if(this.preHoverGroup){
- if(!group || this.preHoverGroup.uuid !== group.uuid){
- if(this.preHoverGroup.userData.$vue){
- this.preHoverGroup.userData.$vue.$emit('mouseout', this.preHoverGroup);
- }else{
- this.emit('mouseout', this.preHoverGroup);
+ if (this.preHoverGroup) {
+ if (!group || this.preHoverGroup.uuid !== group.uuid) {
+ if (this.preHoverGroup.userData.$vue) {
+ this.preHoverGroup.userData.$vue.$emit(
+ "mouseout",
+ this.preHoverGroup
+ );
+ } else {
+ this.emit("mouseout", this.preHoverGroup);
}
this.preHoverGroup.userData.isHover = false;
}
@@ -258,13 +282,24 @@ class CustomThreeLayer extends ThreeLayer{
// window.pageYOffset 鼠标滚动的距离
// clientTop 一个元素顶部边框的宽度
- const offsetTop = getBoundingClientRect.top + window.pageYOffset - client.clientTop;
- const offsetLeft = getBoundingClientRect.left + window.pageXOffset - client.clientLeft;
- this.mouse.x = ((e.x + window.pageXOffset - offsetLeft) / getBoundingClientRect.width) * 2 - 1;
- this.mouse.y = -((e.y + window.pageYOffset - offsetTop) / getBoundingClientRect.height) * 2 + 1;
+ const offsetTop =
+ getBoundingClientRect.top + window.pageYOffset - client.clientTop;
+ const offsetLeft =
+ getBoundingClientRect.left + window.pageXOffset - client.clientLeft;
+ this.mouse.x =
+ ((e.x + window.pageXOffset - offsetLeft) / getBoundingClientRect.width) *
+ 2 -
+ 1;
+ this.mouse.y =
+ -((e.y + window.pageYOffset - offsetTop) / getBoundingClientRect.height) *
+ 2 +
+ 1;
const camera = this.camera;
this.raycaster?.setFromCamera(this.mouse, camera as Camera);
- const intersects = this.raycaster?.intersectObjects([this.scene as Scene], true);
+ const intersects = this.raycaster?.intersectObjects(
+ [this.scene as Scene],
+ true
+ );
const length = intersects?.length;
if (length && length > 0) {
let group: Object3D | null = null;
@@ -281,7 +316,7 @@ class CustomThreeLayer extends ThreeLayer{
}
_getGroup(object: Object3D): Object3D | null {
- if(!object){
+ if (!object) {
return null;
}
if (object.userData.acceptEvent) {
@@ -296,13 +331,12 @@ class CustomThreeLayer extends ThreeLayer{
this.envMap.dispose();
this.envMap = null;
}
+ this.cssRenderer?.domElement?.remove();
super.destroy();
this.lightTypes = null as any;
this.raycaster = undefined;
// this.mouse = undefined;
}
-
-
}
-export default CustomThreeLayer
+export default CustomThreeLayer;
diff --git a/src/vue-amap-extra/packages/ThreeLayer/ThreeLayer.vue b/src/vue-amap-extra/packages/ThreeLayer/ThreeLayer.vue
index ac80d5010ae0f9b275e2c7a671e655fae164a932..025496c092228b0464a0481f1025d5e94f003850 100644
--- a/src/vue-amap-extra/packages/ThreeLayer/ThreeLayer.vue
+++ b/src/vue-amap-extra/packages/ThreeLayer/ThreeLayer.vue
@@ -2,101 +2,109 @@
diff --git a/test/views/three/Gltf.vue b/test/views/three/Gltf.vue
index a84a84db0e8aaf31345fddad12258b511c98dc18..7240b63f103d4d00a6abc9ff1b4484217a91ec44 100644
--- a/test/views/three/Gltf.vue
+++ b/test/views/three/Gltf.vue
@@ -7,43 +7,40 @@
view-mode="3D"
:pitch="60"
:show-building-block="false"
- :features="['bg','road']"
+ :features="['bg', 'road']"
@click="clickMap"
@init="initMap"
>
-
+
-
+
+
-
-
+