react-xrplayer
Version:
An excellent xr player for react
319 lines (296 loc) • 12.6 kB
JavaScript
/**
* 向3D场景中添加可以交互的热点标签或者按钮
*/
import * as THREE from 'three';
import { Radius } from '../const/PanoConst';
import TWEEN from '@tweenjs/tween.js';
class SpriteShapeHelper {
constructor(scene, camera, renderer, container) {
this.scene = scene;
this.camera = camera;
this.renderer = renderer;
this.container = container;
this.hotSpotMap = null; // 热点标签数据
this.hotSpotMeshMap = null; // 热点标签Mesh Map,便于动态缩减
this.pointGroup = null; // 场景中的热点组合
this.objectClickHandler = null;
this.tagClickHandler = null;
this.isTipVisible = true;
this.hotSpotClickable = true;
}
setIsTipVisible = (enable) => {
this.isTipVisible = enable;
let display = "none";
if (enable) {
display = "block";
} else {
display = "none";
}
for (var i = 0; i < this.pointGroup.children.length; i++) {
var name = this.pointGroup.children[i].name;
var tip = document.getElementById(name);
if (enable) {
tip.style.display = display;
} else {
tip.style.display = display;
}
}
}
resetHotSpotGroup = () => {
if (!this.pointGroup) {
this.pointGroup = new THREE.Group();
this.scene.add(this.pointGroup);
this.hotSpotMeshMap = new Map();
this.bindEvent();
}
}
getPointObjects = () => {
if (this.pointGroup && this.pointGroup.children) {
return this.pointGroup.children;
} else {
return [];
}
}
setHotSpotList = (hot_spot_list) => {
this.resetHotSpotGroup();
this.hotSpotMap = new Map(hot_spot_list);
this.hotSpotMap.forEach((value, key) => {
this.createPoint(key, value)
});
}
addHotSpot = (hot_spot) => {
if (!this.pointGroup) {
this.resetHotSpotGroup();
}
this.createPoint(hot_spot.key, hot_spot.value)
}
removeHotSpot = (hot_spot_key) => {
const mesh = this.hotSpotMeshMap.get(hot_spot_key);
if (mesh) {
this.pointGroup.remove(mesh);
}
var tip = document.getElementById(hot_spot_key);
if (tip) {
this.container.removeChild(tip);
}
}
contertSph2Rect = (lat, lon) => {
let r = Radius;
const phi = THREE.Math.degToRad(90 - lat);
const theta = THREE.Math.degToRad(lon);
return [
r * Math.sin(phi) * Math.cos(theta),
r * Math.sin(phi) * Math.sin(theta),
r * Math.cos(phi)
];
}
createPoint(key, value) {
let { lat, lon, res_url, opacity = 1, scale = 16,
animate = false, title = null, img_url = null,
img_height = 100, img_width = 100, title_width } = value;
let position = this.contertSph2Rect(lat, lon);
let meshGroup = new THREE.Group();
meshGroup.name = key;
meshGroup.position.set(...position);
let mesh = this.createSpriteShape(res_url, opacity, scale);
mesh.name = key;
mesh.position.set(...position);
mesh.meshType = 'markIcon';
this.hotSpotMeshMap.set(key, mesh);
this.pointGroup.add(mesh);
if (animate) {
this.animatePoint(mesh);
}
if (img_url || title) {
var div = document.createElement("div");
div.id = key;
div.addEventListener('click', () => {
if (!this.hotSpotClickable) return;
this.tagClickHandler && this.tagClickHandler(key);
}, false)
div.style = "padding:10px 10px;cursor:pointer;background-size: 100% 100%;background-image:url('https://live360.oss-cn-beijing.aliyuncs.com/xr/fuzhou/fz_di.png');color:#fff;display:none;position:absolute;border-radius:6px; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none;font-size:0.85rem;";
if (title_width) div.style.width = title_width + 'px';
}
if (img_url) {
let img = document.createElement("img");
img.src = img_url;
img.alt = "image";
img.height = img_height;
img.width = img_width;
img.align = "left";
div.appendChild(img);
this.container.appendChild(div);
}
if (title) {
let text = document.createElement("div");
text.innerText = title;
text.style = "text-align:center;margin-bottom:45px"
div.appendChild(text);
}
if (img_url || title) this.container.appendChild(div);
}
createSpriteShape = (url, opacity = 1, scale = 16) => {
let texture = new THREE.TextureLoader().load(url);
texture.needsUpdate = true; //注意这句不能少
let material = new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: opacity,
depthTest: false
});
let mesh = new THREE.Sprite(material);
mesh.scale.set(scale * 2, scale * 2, 1);
return mesh;
}
markTitleInViews = () => {
var camera = this.camera;
for (var i = 0; i < this.pointGroup.children.length; i++) {
var name = this.pointGroup.children[i].name;
var tip = document.getElementById(name);
if (tip) {
var wpVector = new THREE.Vector3();
var pos = this.pointGroup.children[i].getWorldPosition(wpVector)
.applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);
if (this.isTipVisible && (pos.x >= -1 && pos.x <= 0.5) && (pos.y >= -1 && pos.y <= 1) && (pos.z >= -1 && pos.z <= 1)) {
var hotSpot = this.hotSpotMap.get(name);
let position = "top";
if (hotSpot != null && hotSpot.hasOwnProperty("position")) {
position = hotSpot.position;
}
var screenPos = this.objectPosToScreenPos(this.pointGroup.children[i], this.container, this.camera);
if (position === 'top') {
tip.style.display = "block";
tip.style.left = screenPos.x - tip.clientWidth / 2 + "px";
tip.style.top = screenPos.y - tip.clientHeight - 30 + "px";
} else if (position === 'bottom') {
tip.style.display = "block";
tip.style.left = screenPos.x - tip.clientWidth / 2 + "px";
tip.style.top = screenPos.y + 30 + "px";
} else if (position === 'left') {
tip.style.display = "block";
tip.style.left = screenPos.x - tip.clientWidth - 30 + "px";
tip.style.top = screenPos.y - tip.clientHeight / 2 + 30 + "px";
} else if (position === 'right') {
tip.style.display = "block";
tip.style.left = screenPos.x + 30 + "px";
tip.style.top = screenPos.y - tip.clientHeight / 2 + 30 + "px";
} else if (position === 'middle') {
tip.style.display = "block";
tip.style.left = screenPos.x - tip.clientWidth / 2 + 30 + "px";
tip.style.top = screenPos.y - tip.clientHeight / 2 + 30 + "px";
}
else {
tip.style.display = "none";
}
} else {
tip.style.display = "none";
}
}
this.pointGroup.children[i].lookAt(this.camera.position);
}
}
objectPosToScreenPos = (object, container, camera) => {
var vector = new THREE.Vector3();
vector.setFromMatrixPosition(object.matrixWorld).project(camera);
var x2hat = vector.x,
y2hat = vector.y;
var W = container.clientWidth;
var H = container.clientHeight;
var pos = new THREE.Vector2();
pos.x = (W / 2) * (x2hat + 1);
pos.y = (H / 2) * (1 - y2hat);
return pos;
}
getBackgroundTexture = (color, opacity, scale) => {
let canvas = document.createElement("canvas");
canvas.click((e) => {
console.log('canvas', '点击了热点');
})
const container = document.getElementById('display')
container.appendChild(canvas);
canvas.width = 128;
canvas.height = 128;
let ctx = canvas.getContext("2d");
ctx.fillStyle = color;
ctx.arc(64, 64, 64, 0, 2 * Math.PI);
ctx.fill();
let texture = new THREE.Texture(canvas);
texture.needsUpdate = true; //注意这句不能少
let material = new THREE.SpriteMaterial({
map: texture,
transparent: true,
opacity: opacity,
depthTest: false
});
let mesh = new THREE.Sprite(material);
mesh.scale.set(scale * 2, scale * 2, 1);
return mesh;
}
animatePoint = (mesh) => {
let t = 300;
let scale = mesh.scale;
let tweenA = new TWEEN.Tween(scale)
.to({ x: scale.x * 0.8, y: scale.y * 0.8 }, 500)
.delay(100)
let tweenB = new TWEEN.Tween(scale)
.to({ x: scale.x * 1.2, y: scale.y * 1.2 }, 500)
.delay(100)
tweenA.chain(tweenB);
tweenB.chain(tweenA);
tweenA.start(t = t + 100);
}
animatePoints = (meshGroup) => {
meshGroup.children.forEach(item => {
this.animatePoint(item);
})
}
update = () => {
if (this.pointGroup) {
this.markTitleInViews();
}
}
getIntersects = (event) => {
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2(); // 鼠标的二维设备坐标
//将屏幕点击的屏幕坐标转化为三维画面平面的坐标,值的范围为-1到1.
const { x: domX, y: domY } = this.renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - domX) / this.renderer.domElement.clientWidth) * 2 - 1;
mouse.y = - ((event.clientY - domY) / this.renderer.domElement.clientHeight) * 2 + 1;
//从相机发射一条射线,经过鼠标点击位置
// mouse为鼠标的二维设备坐标,camera为射线起点处的相机
raycaster.setFromCamera(mouse, this.camera);
// 射线与模型的交点,这里交点会是多个,因为射线是穿过模型的,
//与模型的所有mesh都会有交点,但我们选取第一个,也就是intersects[0]。
const meshArray = Array.from(this.hotSpotMeshMap.values());
return raycaster.intersectObjects(meshArray);
}
setHotSpotClickable = (enable) => {
this.hotSpotClickable = enable;
}
bindEvent = () => {
document.addEventListener('click', (event) => {
event.preventDefault();
console.log('检测热点点击');
var intersects = this.getIntersects(event);
//如果只需要将第一个触发事件,那就取数组的第一个模型
if (intersects.length > 0) {
if (this.objectClickHandler) {
console.log('intersects', intersects);
this.objectClickHandler(intersects);
}
}
}, true);
document.addEventListener('mousemove', (event) => {
event.preventDefault();
var intersects = this.getIntersects(event);
//如果只需要将第一个触发事件,那就取数组的第一个模型
if (intersects.length > 0) {
document.getElementById('canvas').style.cursor = 'pointer';
}
else {
document.getElementById('canvas').style.cursor = 'default';
}
}, true);
}
}
export default SpriteShapeHelper;