import * as THREE from 'three';
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
import { HIGHT_LIGHT_STYLES, CAMERA_FAR } from '@/constants';
import { nameUtils } from '@/util/nameManager';
import { type EditorSceneSetup } from '@/view/hscope/editor/scene/editorSceneSetup';
import { setMaterials } from './index';
const NORMAL_COLOR = 0x00ffff; // 蓝色;
const TEXT_SCALE = 30; // 字体大小
const SEARCH_OPACITY = 0.6;
export type HighlightTextType = 'refdes' | 'sensingValue';
class HightLightStyle {
meshList: THREE.Mesh[];
hightLightLineGroup: THREE.Group;
textGroup: THREE.Group;
textVisibleMap: Object;
refdesToAttributeMap: Object;
mapStyleFunction: Map<string, (mesh: THREE.Mesh<THREE.BufferGeometry, THREE.Material | THREE.Material[]>) => void>;
editorScene: EditorSceneSetup;
transparencyMeshMap: Map<THREE.Mesh, THREE.Mesh[]> = new Map();
#selectedMeshLamberMaterial = new THREE.MeshPhongMaterial({
color: NORMAL_COLOR,
opacity: SEARCH_OPACITY,
polygonOffset: true, // 开启偏移,避免器件之间存在重叠渲染相互影响
polygonOffsetFactor: 1, // 偏移因子
polygonOffsetUnits: -1, // 偏移值
side: THREE.DoubleSide,
});
initValue(
scene: THREE.Scene,
refdesToAttribute: Object,
highlight: Object,
editorScene: EditorSceneSetup,
) {
this.hightLightLineGroup = new THREE.Group();
this.textGroup = new THREE.Group();
this.textVisibleMap = highlight;
this.refdesToAttributeMap = refdesToAttribute;
this.mapStyleFunction = new Map();
this.mapStyleFunction.set(HIGHT_LIGHT_STYLES.NORMAL, mesh => this.normalStyle(mesh));
this.mapStyleFunction.set(HIGHT_LIGHT_STYLES.SEARCH_CHANGE_LIGHT, mesh => this.searchAndSelectedLightStyle(mesh));
this.mapStyleFunction.set(HIGHT_LIGHT_STYLES.DRAG_LIGHT, mesh => this.dragLightStyle(mesh));
this.editorScene = editorScene;
scene.add(this.hightLightLineGroup);
scene.add(this.textGroup);
}
createLine(mesh: THREE.Mesh, color: number) {
if (this.hightLightLineGroup?.children?.length) {
let findTemp = this.hightLightLineGroup.children.find(item => item.name === mesh.name);
if (findTemp) {
let findMaterial = (findTemp as THREE.LineSegments).material;
findMaterial.color.set(new THREE.Color(color));
findMaterial.needsUpdate = true;
return;
}
}
let line = this.meshLineSegments(mesh, color);
line.name = mesh.name;
this.hightLightLineGroup.add(line);
}
meshLineSegments(mesh: THREE.Mesh, color = NORMAL_COLOR) {
// 模型线条显示角度80以上的
let edges = new THREE.EdgesGeometry(mesh.geometry, 80);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color }));
line.name = mesh.name;
line.position.copy(mesh.getWorldPosition(new THREE.Vector3()));
const quaternion = mesh.getWorldQuaternion(new THREE.Quaternion());
const euler = new THREE.Euler().setFromQuaternion(quaternion);
line.rotation.copy(euler);
return line;
}
getSide(name: string) {
let side = nameUtils.getMeshBaseBoardSide(name);
if (side === 'Top' || side === 'Bottom') {
return side;
}
if (name.includes('Top=')) {
side = 'Top';
} else {
side = 'Bottom';
}
return side;
}
createLabelDiv(text: string, type: HighlightTextType): HTMLDivElement {
const fontSize = 14;
const labelDiv = document.createElement('div');
labelDiv.style.fontSize = `${fontSize}px`;
labelDiv.className = `highlight-text-content ${type}`;
labelDiv.innerText = text;
labelDiv.style.color = 'white';
labelDiv.hidden = !this.textVisibleMap[type].isChecked;
return labelDiv;
}
createTextCSS2D(mesh: THREE.Mesh) {
const refdes = this.getMeshRefdes(mesh.name);
const refdesDiv = this.createLabelDiv(refdes, 'refdes');
const attributes = this.refdesToAttributeMap[refdes];
const attrList = attributes ? Object.entries(attributes) : [];
const divs = attrList.map(([type, text]) => this.createLabelDiv(text as string, type as HighlightTextType));
const labelContainer = document.createElement('div');
labelContainer.className = `highlight-text-group ${refdes}`;
labelContainer.append(refdesDiv, ...divs);
return new CSS2DObject(labelContainer);
}
getMeshRefdes(meshName: string) {
let refdes = nameUtils.getMeshRefdes(meshName);
if (!refdes) {
let listName = meshName.split('&').pop();
refdes = listName?.split('=')[0] ?? '';
}
return refdes;
}
createTextRefdes(mesh: THREE.Mesh) {
if (!mesh?.name || nameUtils.isBaseBoard(mesh?.name)) {
return;
}
let findTemp = this.textGroup?.children.find(item => item.name === mesh.name);
if (findTemp) {
return;
}
const textSprite = this.createTextCSS2D(mesh);
if (!textSprite) {
return;
}
textSprite.name = mesh.name;
const boxValue = new THREE.Box3().setFromObject(mesh);
if (!boxValue) {
return;
}
let side = this.getSide(mesh.name);
// 取包围盒X,Y轴中心点 字体比器件高2像素
let pos = new THREE.Vector3(
(boxValue?.max.x + boxValue?.min.x) * 0.5,
(boxValue?.max.y + boxValue?.min.y) * 0.5,
side === 'Top' ? boxValue?.max.z + 2 : boxValue?.min.z - 2,
);
// 设置固定字体大小
let textScale = TEXT_SCALE;
textSprite.position.copy(pos);
textSprite.scale.set(textScale, textScale, textScale);
this.textGroup.add(textSprite);
}
/**
* @description 选中器件的高亮效果
*/
normalStyle(mesh: THREE.Mesh) {
this.createLine(mesh, NORMAL_COLOR);
this.createTextRefdes(mesh);
mesh.material = this.#selectedMeshLamberMaterial;
}
/**
* @description 搜索后所有器件的高亮效果
*/
searchLightStyle(mesh: THREE.Mesh) {
this.createTextRefdes(mesh);
mesh.material = this.#selectedMeshLamberMaterial;
}
/**
* @description 搜索后上下列框器件的高亮效果
*/
searchAndSelectedLightStyle(mesh: THREE.Mesh) {
this.createTextRefdes(mesh);
mesh.material = this.#selectedMeshLamberMaterial;
}
/**
* @description 框选器件的高亮效果
*/
dragLightStyle(mesh: THREE.Mesh) {
this.createLine(mesh, NORMAL_COLOR);
mesh.material = this.#selectedMeshLamberMaterial;
}
clearHightLight(mesh: THREE.Mesh) {
if (!mesh?.name) {
return;
}
let temp = this.hightLightLineGroup?.children.find(item => item.name === mesh.name);
if (temp) {
temp.geometry.dispose();
temp.material.dispose();
this.hightLightLineGroup.remove(temp);
}
if (mesh?.originMaterial) {
setMaterials(mesh, mesh.originMaterial);
}
if (nameUtils.isBaseBoard(mesh?.name) || !this.textGroup?.children?.length) {
return;
}
let findTemp = this.textGroup?.children.find(item => item.name === mesh.name);
if (findTemp) {
this.textGroup.remove(findTemp);
findTemp.clear();
}
this.removeTransparency(mesh);
}
doHightLight(mesh: THREE.Mesh, styleStr = HIGHT_LIGHT_STYLES.NORMAL) {
if (!mesh) {
return;
}
if (!mesh?.originMaterial) {
mesh.originMaterial = mesh.material;
}
if (this.mapStyleFunction.has(styleStr)) {
let func = this.mapStyleFunction.get(styleStr);
func && func(mesh);
}
}
updateLinePosition(mesh: THREE.Mesh) {
if (!mesh?.name) {
return;
}
let tempLine = this.hightLightLineGroup?.children.find(item => item.name === mesh.name);
if (tempLine) {
tempLine.position.copy(mesh.getWorldPosition(new THREE.Vector3()));
const quaternion = mesh.getWorldQuaternion(new THREE.Quaternion());
const euler = new THREE.Euler().setFromQuaternion(quaternion);
tempLine.rotation.copy(euler);
}
}
updateTextPos(mesh: THREE.Mesh) {
if (!mesh?.name) {
return;
}
if (nameUtils.isBaseBoard(mesh?.name) || !this.textGroup?.children?.length) {
return;
}
let findTemp = this.textGroup?.children.find(item => item.name === mesh.name);
const boxValue = new THREE.Box3().setFromObject(mesh);
if (!boxValue || !findTemp) {
return;
}
// 取包围盒X,Y轴中心点
let pos = new THREE.Vector3(
(boxValue?.max.x + boxValue?.min.x) * 0.5,
(boxValue?.max.y + boxValue?.min.y) * 0.5,
boxValue?.max.z,
);
findTemp.position.copy(pos);
}
/**
* @description 修改器件的位号,高亮的位号跟着修改,框的name也需修改
*/
modifyRefdes(oldName: string, newMesh: THREE.Mesh) {
let findTemp = this.textGroup?.children.find(item => item.name === oldName);
if (findTemp) {
this.textGroup.remove(findTemp);
findTemp.clear();
this.createTextRefdes(newMesh);
}
let findLine = this.hightLightLineGroup?.children.find(item => item.name === oldName);
if (findLine) {
findLine.name = newMesh.name;
}
}
/**
* @description 隐藏或显示器件时,框跟位号跟随隐藏或显示
*/
displayHLight(mesh: THREE.Mesh, isV: boolean) {
let meshText = this.textGroup?.children.find(item => item.name === mesh.name);
if (meshText) {
meshText.element.hidden = !isV;
}
let meshLine = this.hightLightLineGroup?.children.find(item => item.name === mesh.name);
if (meshLine) {
meshLine.visible = isV;
}
}
showRefdesAccordingCamera(flag: number) {
let display;
if (flag < 0) {
display = 'Top';
} else {
display = 'Bottom';
}
this.textGroup?.children.forEach(child => {
if (child.name) {
let side = this.getSide(child.name);
child.element.hidden = side !== display;
}
});
}
switchTextVisibleByType(type: HighlightTextType) {
this.textVisibleMap[type].isChecked = !this.textVisibleMap[type].isChecked;
this.textGroup?.children.forEach(child => {
const textDiv = child.element.querySelector(`.${type}`);
if (textDiv) {
textDiv.hidden = !this.textVisibleMap[type].isChecked;
}
});
}
/**
*@description 当被遮挡的时候透明化
* @memberof HightLightStyle
*/
async doOccludedTransparency(selectedList: Array<{ object: THREE.Mesh }>) {
selectedList.forEach(selected => {
const highLightMesh = selected.object;
if (!highLightMesh || nameUtils.isBaseBoard(highLightMesh.name)) {
return;
}
const baseBoardSide = nameUtils.getMeshBaseBoardName(highLightMesh.name);
const correctViewCamera = this.editorScene.oCamera.clone();
if (baseBoardSide === 'Top') {
correctViewCamera.position.set(0, 0, CAMERA_FAR);
correctViewCamera.up.set(0, 0, 1);
} else {
correctViewCamera.position.set(Math.cos(-Math.PI / 2), Math.sin(-Math.PI / 2), - CAMERA_FAR);
correctViewCamera.up.set(0, 0, -1);
}
const meshBox3 = new THREE.Box3().setFromObject(highLightMesh);
const raycasterTestPoints = [
new THREE.Vector3((meshBox3.min.x + meshBox3.max.x) / 2, (meshBox3.min.y + meshBox3.max.y) / 2, meshBox3.max.z),
];
const occludedMeshList = this.getOccludedByRaycaster(
raycasterTestPoints,
correctViewCamera,
this.getComponentGroup(highLightMesh),
);
occludedMeshList.forEach(intersect => {
if (intersect instanceof THREE.Mesh
&& !nameUtils.isBaseBoard(intersect.name)
&& intersect.name !== highLightMesh.name
) {
this.addTransparency(highLightMesh, intersect);
}
});
});
}
/**
* @description 包围盒四角或者中心点射线相交的器件将被透明化
* @param raycasterTestPoints 射线器件
* @param camera
* @param componentGroup 当前的器件group
* @returns
*/
getOccludedByRaycaster(
raycasterTestPoints: THREE.Vector3[],
camera: THREE.OrthographicCamera,
componentGroup: THREE.Group | null,
): THREE.Object3D[] {
const raycaster = new THREE.Raycaster();
let result: THREE.Object3D[] = [];
raycasterTestPoints.forEach(point => {
const pos = point.project(camera);
raycaster.setFromCamera(new THREE.Vector2(pos.x, pos.y), camera);
const insertGroup = componentGroup ?? this.editorScene.group;
const intersects = raycaster.intersectObject(insertGroup);
result.push(...intersects.map(item => item.object));
});
return Array.from(new Set(result));
}
/**
* 获取当前的器件的group
* @param {THREE.Object3D} curObject3D
* @returns {(THREE.Object3D | null)}
* @memberof HightLightStyle
*/
getComponentGroup(curObject3D: THREE.Object3D): THREE.Object3D | null {
if (!curObject3D) {
return null;
}
if (curObject3D.parent?.name.endsWith('Components')) {
return curObject3D.parent;
}
if (curObject3D?.parent) {
return this.getComponentGroup(curObject3D.parent);
}
return null;
}
addTransparency(highLightMesh: THREE.Mesh, occludedMesh: THREE.Mesh) {
if (!occludedMesh.originMaterial) {
occludedMesh.originMaterial = occludedMesh.material;
}
const transparencyList = this.transparencyMeshMap.get(highLightMesh);
if (transparencyList && transparencyList.includes(occludedMesh)) {
return;
}
if (transparencyList && !transparencyList.includes(occludedMesh)) {
transparencyList.push(occludedMesh);
}
if (!transparencyList) {
this.transparencyMeshMap.set(highLightMesh, [occludedMesh]);
}
const newMaterial = occludedMesh.originMaterial.clone();
newMaterial.transparent = true;
newMaterial.opacity = 0.2;
setMaterials(occludedMesh, newMaterial);
}
/**
*@description 移除透明效果
* @param {THREE.Mesh} unHighlightMesh
* @returns void
* @memberof HightLightStyle
*/
removeTransparency(unHighlightMesh: THREE.Mesh) {
const transparencyMeshList = this.transparencyMeshMap.get(unHighlightMesh);
if (!transparencyMeshList) {
return;
}
this.transparencyMeshMap.delete(unHighlightMesh);
transparencyMeshList.forEach(transparencyMesh => {
// 其他高亮的器件也没被该透明器件遮挡才可以取消透明化
if (Object.values(this.transparencyMeshMap).find(meshList => meshList.include(transparencyMesh))) {
return;
}
setMaterials(transparencyMesh, transparencyMesh.originMaterial);
});
}
}
let hightLightStyleInstance = new HightLightStyle();
export default hightLightStyleInstance;
最新发布