Three.js基础功能学习十五:智能黑板实现实例二

续上章继续实现智能黑板示例:

使用Vue3+Elementplus+Threejs实现智能黑板,实现基础的几何图形、三维图形、手写板的元素动态绘制效果,并实现数学、物理、化学、地理、生物等课程常见的图形元素及函数图形等。

源码下载地址: 点击下载
效果演示:









一、学习视频

https://www.bilibili.com/video/BV1JT69BUEdD/

二、图形绘制基础框架

2.1. 绘制方法汇总

javascript 复制代码
import { ThreeMode } from "./model/ThreeModel";

//三维的实例
export const instance:ThreeMode = new ThreeMode();

export default {
    ...instance,
    init:instance.init.bind(instance),
    dispose:instance.dispose.bind(instance),
    animate:instance.animate.bind(instance),
    resizeRendererToDisplaySize:instance.resizeRendererToDisplaySize.bind(instance),
    setBackground:instance.setBackground.bind(instance),
    setSceneTexture:instance.setSceneTexture.bind(instance),
    setSceneEnvironment:instance.setSceneEnvironment.bind(instance),
    setBackgroundBlurriness:instance.setBackgroundBlurriness.bind(instance),
    setLightColor:instance.setLightColor.bind(instance),
    setLightIntensity:instance.setLightIntensity.bind(instance),
    setCameraFov:instance.setCameraFov.bind(instance),
    setCameraFar:instance.setCameraFar.bind(instance),
    setCameraNear:instance.setCameraNear.bind(instance),
    setCameraZoom:instance.setCameraZoom.bind(instance),
    stopMesh3D:instance.stopMesh3D.bind(instance),
    stopThandWriting:instance.stopThandWriting.bind(instance),
    pauseMesh3D:instance.pauseMesh3D.bind(instance),
    pauseMesh2D:instance.pauseMesh2D.bind(instance),
    startMesh2D:instance.startMesh2D.bind(instance),
    stopMesh2D:instance.stopMesh2D.bind(instance),
    startMesh3D:instance.startMesh3D.bind(instance),
    starThandWriting:instance.starThandWriting.bind(instance),
    setControlEnabled:instance.setControlEnabled.bind(instance),
    setCameraHelper:instance.setCameraHelper.bind(instance),
    setAxesHelper:instance.setAxesHelper.bind(instance),
    clear:instance.clear.bind(instance),
    add:instance.add.bind(instance),
    sun:instance.sun.bind(instance),
    reset:instance.reset.bind(instance),
    startEraser:instance.startEraser.bind(instance),
    stopEraser:instance.stopEraser.bind(instance),
    setHandWritingPlaneColor:instance.setHandWritingPlaneColor.bind(instance),
    setHandWritingPlaneOpacity:instance.setHandWritingPlaneOpacity.bind(instance),
    setHandWritingPlaneTexture:instance.setHandWritingPlaneTexture.bind(instance),
    setHandWritingLineColor:instance.setHandWritingLineColor.bind(instance),
    setHandWritingLineWidth:instance.setHandWritingLineWidth.bind(instance),
    setEraserColor:instance.setEraserColor.bind(instance),
    setEraserRadius:instance.setEraserRadius.bind(instance),
    setMeshD2PlanColor:instance.setMeshD2PlanColor.bind(instance),
    setMeshD2PlanOpacity:instance.setMeshD2PlanOpacity.bind(instance),
    setMeshD2PlanTexture:instance.setMeshD2PlanTexture.bind(instance),
    setMeshLineColor:instance.setMeshLineColor.bind(instance),
    setMeshLineWidth:instance.setMeshLineWidth.bind(instance),
    setMeshFillType:instance.setMeshFillType.bind(instance),
    setMeshFillColor:instance.setMeshFillColor.bind(instance),
};

2.2. model数据结构定义

UpdateConfig.ts

javascript 复制代码
import * as THREE from "three"
/**
 * 鼠标移动的事件
 */
export default  interface UpdateConfig {
    /**
     * 创建物体
     */
    (mesh:THREE.Mesh,value:any):void;
}

OptionData.ts

javascript 复制代码
export interface OptionData{
    value:Object,
    label:String
}

OprationModes.ts

javascript 复制代码
/**
 * 当前操作的模式信息
 */
export enum OprationModes{
    D2,//二维模式
    D3,//三维模式
    HandWriting//手写模式
}

MouseStatus.ts

javascript 复制代码
/**
 * 当前鼠标操作的状态
 */
export enum MouseStatus{
    None,//空闲状态
    HandWriting,//手写状态
    Create//绘制状态
}

D3Config.ts

javascript 复制代码
import * as THREE from "three"
/**
 * 图形填充的纹理
 */
export enum FillType{
    Color,
    Texture,
    Customer,
    GradientColor
}
/**
 * 自定义纹理
 */
export interface CustomerTexture{
    /**
     * 创建物体
     */
    ():THREE.Texture;
}
/**
 * 三维相关的配置
 */
export interface D3Config{
    maxSize:number,
    camera:{
        fov:number,
        near:number,
        far:number,
        zoom:number,
        position:THREE.Vector3,
    },
    scene:{
        background:THREE.Color|null,
        texture:THREE.Texture|null,
        environment:THREE.Texture|null,
        backgroundBlurriness:number
    },
    light:{
        intensity:number,
        color:THREE.Color
    },
    handWriting:{//手写样式的定义
        planeColor:THREE.Color,
        planeOpacity:number,
        planeTexture:string|null,
        line:{
            width:number,
            color:THREE.Color
        },
    },
    eraser:{//板擦相关的配置
        color:THREE.color,
        radius:number
    },
    mesh:{
        //二维时候的背景板的颜色
        d2PlaneColor:THREE.Color,
        d2PlaneOpacity:number,
        d2PlaneTexture:string|null,
        //物体的线条颜色
        line:{
            width:number,
            color:THREE.Color
        },
        //物体的填充颜色
        fill:{
            type:FillType,
            color:THREE.Color|null,
            texture:THREE.Texture|null,
            customer:CustomerTexture|null
        },
    }
}

ToolBartem.ts

javascript 复制代码
import type { ToolBarMeshItem } from "./ToolBarMeshItem";

/**
 * 工具栏按钮的数据类型定义
 */
export interface ToolBarItem{
    /**
     * 名字
     */
    name:string,
    /**
     * 图标
     */
    icon:string,
    /**
     * 标识
     */
    key:string,
    /**
     * 关联项
     */
    fkey?:string,
    /**
     * 命令标识,通过这个标识处理对应的操作
     */
    commond?:string,
    /**
     * 当前按钮是否被选中
     */
    selected?:boolean,
    /**
     * 状态关联项,即关联的工具栏状态变动之后,此处的状态也要跟着变动
     */
    status?:string[],
    /**
     * 下级
     */
    children?:ToolBarItem[]|ToolBarMeshItem[]
}

ToolBarMeshItem.ts

javascript 复制代码
import * as THREE from "three"
import type { ToolBarItem } from "./ToolBartem"
import TabItemContentItem  from "./TabItemContentItem";
import type { OptionData } from "../OptionData";
/**
 * 创建物体的方法
 */
export interface CreateMeshFunction {
    /**
     * 创建物体
     */
    (point:THREE.Vector3):THREE.Mesh;
}
/**
 * 创建物体的形状方法
 */
export interface CreateGeometryFunction {
    /**
     * 创建物体
     */
    ():THREE.BufferGeometry;
}
/**
 * 创建物体的材质方法
 */
export interface CreateMaterialFunction {
    /**
     * 创建物体
     */
    ():THREE.Material;
}
/**
 * 鼠标移动的事件
 */
export interface MouseMoveFunction {
    /**
     * 创建物体
     */
    (point:THREE.Vector2,mesh:THREE.Mesh,event:MouseEvent):void;
}
/**
 * 创建物体的方法
 */
export interface CreateFunction {
    /**
     * 创建物体
     */
    (point:THREE.Vector3):THREE.Mesh;
}

/**
 * 结束物体的创建过程
 */
export interface EndDrawFunction {
    /**
     * 创建物体
     */
    (mesh:THREE.Mesh):void;
}
/**
 * 这种类型的物体的编辑配置项
 */
export class ToolBarMeshItemConfigItem<T extends number | string | THREE.Color | null | THREE.Texture | THREE.Material> extends TabItemContentItem{
    value:T;//THREE.Material|THREE.Texture|string|null|number|THREE.Color,
    form?:boolean
    
    
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: T,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        min,
        max,
        step,
        options,
        auto);
        this.value = value;
        this.form = form;
    }
    
    updateConfig(mesh:THREE.Mesh, value:T) {
        mesh.material.size = value;
    }
}


export class ToolBarMeshSizeItemConfigItem extends ToolBarMeshItemConfigItem<number>{
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: number,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        value,
        auto,
        form,
        min,
        max,
        step,
        options);
    }

    updateConfig(mesh:THREE.Mesh, value:number) {
        mesh.material.size = value;
    }
}

export class ToolBarMeshColorItemConfigItem extends ToolBarMeshItemConfigItem<THREE.Color>{
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: THREE.Color,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        value,
        auto,
        form,
        min,
        max,
        step,
        options);
    }

    updateConfig(mesh:THREE.Mesh, value:THREE.Color) {
        mesh.material.color = value;
    }
}

export class ToolBarMeshTextureItemConfigItem extends ToolBarMeshItemConfigItem<THREE.Texture|string>{
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: THREE.Texture|string,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        value,
        auto,
        form,
        min,
        max,
        step,
        options);
    }

    updateConfig(mesh:THREE.Mesh, value:THREE.Texture) {
    }
}

export class ToolBarMeshMaterialItemConfigItem extends ToolBarMeshItemConfigItem<THREE.Material>{
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: THREE.Material,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        value,
        auto,
        form,
        min,
        max,
        step,
        options);
    }

    updateConfig(mesh:THREE.Mesh, value:THREE.Material) {
    }
}
export class  ToolBarMeshRadiusItemConfigItem  extends ToolBarMeshItemConfigItem<number>{
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: number,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        value,
        auto,
        form,
        min,
        max,
        step,
        options);
    }
    updateConfig(mesh:THREE.Mesh, value:number) {
        mesh.geometry = new THREE.SphereGeometry(value,32,32);
    }
}

/**
 * 这种类型的物体的编辑配置项
 */
export interface ToolBarMeshItemConfig {
    /**
     * 材质
     */
    currentMaterial?:ToolBarMeshMaterialItemConfigItem,
    /**
     * 纹理
     */
    texture?:ToolBarMeshTextureItemConfigItem,
    /**
     * 颜色
     */
    color?:ToolBarMeshColorItemConfigItem,
    /**
     * 颜色
     */
    color1?:ToolBarMeshColorItemConfigItem,
    /**
     * 大小
     */
    size?:ToolBarMeshSizeItemConfigItem,
    /**
     * 大小
     */
    size1?:ToolBarMeshSizeItemConfigItem,
    /**
     * 半径
     */
    radius?:ToolBarMeshRadiusItemConfigItem,
    /**
     * 起始角度
     */
    thetaStart?:ToolBarMeshItemConfigItem<number>,
    /**
     * 中心角
     */
    thetaLength?:ToolBarMeshItemConfigItem<number>,
    /**
     * 大小
     */
    width?:ToolBarMeshItemConfigItem<number>,
    /**
     * 大小
     */
    height?:ToolBarMeshItemConfigItem<number>,
}

/**
 * 工具栏按钮的数据类型定义
 */
export interface ToolBarMeshItem extends ToolBarItem{
    /**
     * 物体的配置参数
     */
    config?:ToolBarMeshItemConfig
    /**
     * 对应的形状信息
     */
    geometry?:CreateGeometryFunction,
    /**
     * 物体的材质
     */
    material?:CreateMaterialFunction,
    /**
     * 具体的物体
     */
    mesh?:CreateMeshFunction,
    /**
     * 鼠标移动
     */
    mouseMove?:MouseMoveFunction,
    /**
     * 结束绘制
     */
    endDraw?:EndDrawFunction,
}

Tool.ts

javascript 复制代码
import type { ToolBarMeshItem } from "./ToolBarMeshItem";
import type { ToolBarItem } from "./ToolBartem";

/**
 * 工具栏按钮的配置
 */
export interface Tool{
    //顶部的工具栏
    top:ToolBarItem[],
    //左边的工具栏
    left:ToolBarMeshItem[],
    //右边的工具栏
    right:ToolBarItem[],
    //底部的工具栏
    bottom:ToolBarItem[],
    //自由按钮的工具栏配置
    free:ToolBarMeshItem[]
}

TabItemContentItem.ts

javascript 复制代码
import type { OptionData } from "../OptionData"
import type UpdateConfig from "../UpdateConfig"
import * as THREE from "three"

/**
 * 不同类型物体的编辑选项
 */
export default class TabItemContentItem{
  type: string
  name: string
  modelPath: string[]
  min?: number
  max?: number
  step?: number
  options?: OptionData[]
  auto?: boolean
  
  constructor(
    type: string,
    name: string,
    modelPath: string[],
    min?: number,
    max?: number,
    step?: number,
    options?: OptionData[],
    auto?: boolean
  ){
    this.type = type;
    this.name = name;
    this.modelPath = modelPath;
    this.min = min;
    this.max = max;
    this.step = step;
    this.options = options;
    this.auto = auto;
  }
}

TabItem.ts

javascript 复制代码
import type { ToolBarItem } from "./ToolBartem";

export interface TabItem {
  name: string;
  contents: ToolBarItem[];
}

BaseMeshItem.ts

javascript 复制代码
import * as THREE from "three"
import type { CreateGeometryFunction, CreateMaterialFunction, CreateMeshFunction, EndDrawFunction, MouseMoveFunction, ToolBarMeshItem, ToolBarMeshItemConfig } from "../ToolItem/ToolBarMeshItem";
import type { ToolBarItem } from "../ToolItem/ToolBartem";
/**
 * 无图工具栏的基础类
 */
export class BaseMeshItem implements ToolBarMeshItem{
    name: string;
    icon: string;
    key: string;
    fkey: string | undefined;
    commond: string | undefined;
    selected: boolean | undefined = false;
    status: string[] | undefined;
    children: ToolBarItem[] | ToolBarMeshItem[] | undefined;

    geometry?: CreateGeometryFunction | undefined;
    material?: CreateMaterialFunction | undefined;
    mesh?: CreateMeshFunction | undefined;
    mouseMove?: MouseMoveFunction | undefined;
    endDraw?: EndDrawFunction | undefined;
    
    
    /**
     * 物体的配置参数
     */
    config?:ToolBarMeshItemConfig;

    constructor(
        name:string,
        icon:string,
        key:string,
        fkey?: string | undefined,
        commond?: string | undefined,
        selected?: boolean | undefined,
        status?: string[] | undefined,
        children?: ToolBarItem[] | ToolBarMeshItem[] | undefined
    ){
        this.name = name;
        this.icon = icon;
        this.key = key;
        this.fkey = fkey;
        this.commond = commond;
        this.selected = selected;
        this.status = status;
        this.children = children;
        this.geometry = this._geometry;
        this.mouseMove = this._mouseMove;
        this.mesh = this._mesh;
        this.endDraw = this._endDraw;
    }

    protected _endDraw(mesh: THREE.Mesh){
        //DO NOTHING
    }
    protected _geometry(){
        return new THREE.BufferGeometry();
    }
    protected _mouseMove(point: THREE.Vector3, mesh: THREE.Mesh,event:MouseEvent) {
        mesh.geometry.setAttribute('position', new THREE.Float32BufferAttribute([point.x, point.y, point.z], 3));
    }
    protected _mesh(point:THREE.Vector3) {
        const geometry = this.geometry?this.geometry():this._geometry();
        // geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [point.x,point.y,point.z+0.1], 3 ) );
        const material = this.material?this.material():new THREE.MeshBasicMaterial();
        let result = new THREE.Mesh(geometry, material)
        result.position.set(point.x,point.y,point.z+0.1);
        return result;
    }
}

2.3. 工具栏定义

Constant.ts

javascript 复制代码
import { FillType } from "@/model/D3Config";

/**
 * 切换课程
 */
export const COMMOND_TYPE_CHANGE_MODEL = "1";

/**
 * 打开弹框
 */
export const COMMOND_TYPE_OPEN_CONFIG_DIALOG = "2";

/**
 * 手写状态
 */
export const COMMOND_TYPE_SELF_WRITE = "3";

/**
 * 三维状态辅助相关的,开听坐标轴、摄像机、轨道等的开关
 */
export const COMMOND_TYPE_D3_HELP = "4";

/**
 * 三维状态操作相关的,清空、放大、缩小
 */
export const COMMOND_TYPE_D3_OP = "5";

/**
 * 手写状态的变动,关闭或开启
 */
export const COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE = "6";

/**
 * 绘制二维物体
 */
export const COMMOND_TYPE_2D_MESH = "7";

/**
 * 绘制三维物体
 */
export const COMMOND_TYPE_3D_MESH = "8";

/**
 * 选中物体
 */
export const COMMOND_TYPE_MESH_SELECTED = "9";

/**
 * 取消选中物体
 */
export const COMMOND_TYPE_MESH_CANCEL_SELECTED = "10";

/**
 * 自定义配置变动
 */
export const COMMOND_TYPE_MESH_CONFIG_CHANGE = "11";

/**
 * 二维模式
 */
export const COMMOND_TYPE_D2_MODEL = "12";

/**
 * 三维模式
 */
export const COMMOND_TYPE_D3_MODEL = "13";

/**
 * 物体填充的类型
 */
export const FILL_TYPE = [
    {
        value: FillType.Color,
        label: "纯色",
    },
    {
        value: FillType.GradientColor,
        label: "渐变色",
    },
    {
        value: FillType.Texture,
        label: "纹理",
    },
    {
        value: FillType.Customer,
        label: "自定义",
    },
];

RectangleMeshItem.ts

javascript 复制代码
import { ToolBarMeshColorItemConfigItem, ToolBarMeshItemConfigItem, ToolBarMeshMaterialItemConfigItem, ToolBarMeshRadiusItemConfigItem, ToolBarMeshSizeItemConfigItem, ToolBarMeshTextureItemConfigItem } from "@/model/ToolItem/ToolBarMeshItem";
import * as THREE from "three"
import { useConfigStore } from "@/stores/config";
import type { OptionData } from "@/model/OptionData";
import { Base2dMeshItem } from "@/model/MeshToolItem/Base2dMeshItem";

export class  ToolBarMeshWidthItemConfigItem  extends ToolBarMeshItemConfigItem<number>{
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: number,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        value,
        auto,
        form,
        min,
        max,
        step,
        options);
    }
    updateConfig(mesh:THREE.Mesh, value:number) {
        mesh.geometry = new THREE.PlaneGeometry(value,mesh.geometry.parameters.height,132);
    }
}

export class  ToolBarMeshHeightItemConfigItem  extends ToolBarMeshItemConfigItem<number>{
    constructor(
        type: string,
        name: string,
        modelPath: string[],
        value: number,
        auto: boolean= false,
        form: boolean = false,
        min?: number,
        max?: number,
        step?: number,
        options?: OptionData[]
    ){
        super(type,
        name,
        modelPath,
        value,
        auto,
        form,
        min,
        max,
        step,
        options);
    }
    updateConfig(mesh:THREE.Mesh, value:number) {
        mesh.geometry = new THREE.PlaneGeometry(mesh.geometry.parameters.width,value,132);
    }
}
/**
 * 长方形
 */
export class RectangleMeshItem extends Base2dMeshItem {

    constructor(name: string, icon: string, key: string, fkey: string) {
        super(name, icon, key, fkey);

        this.geometry = this._geometry;

        this.material = this._material;

        this.mouseMove = this._mouseMove;

        this.config = {
            currentMaterial: new ToolBarMeshMaterialItemConfigItem('select', '材质', ['currentMaterial', 'value'], THREE.MeshBasicMaterial, false, false, undefined, undefined, undefined, [{ value: '', label: '纯色' }]),
            texture: new ToolBarMeshTextureItemConfigItem('select', '纹理', ['currentMaterial', 'value'], '', true, true, undefined, undefined, undefined, [{
                value: '',
                label: '颜色'
            }, {
                value: THREE.VideoTexture,
                label: '视频'
            }, {
                value: THREE.CanvasTexture,
                label: 'Canvas'
            }, {
                value: THREE.DataTexture,
                label: '数据'
            }, {
                value: THREE.Texture,
                label: '自定义'
            }]),
            size: new ToolBarMeshSizeItemConfigItem('slider', '线条尺寸', ['size', 'value'], 1, true, true, 0, useConfigStore()?.d3Config?.maxSize||8, useConfigStore()?.d3Config?.camera?.near || 0.001),
            width: new ToolBarMeshWidthItemConfigItem('slider', '宽度', ['width', 'value'], 1, true, true, 0, useConfigStore()?.d3Config?.maxSize||8, useConfigStore()?.d3Config?.camera?.near || 0.001),
            height: new ToolBarMeshHeightItemConfigItem('slider', '高度', ['height', 'value'], 2, true, true, 0, useConfigStore()?.d3Config?.maxSize||8, useConfigStore()?.d3Config?.camera?.near || 0.001),
            color: new ToolBarMeshColorItemConfigItem('color', '颜色', ['color', 'value'], useConfigStore()?.d3Config?.mesh?.fill?.color || new THREE.Color('#FFFfff'), true, true),
        }
    }

    protected _geometry() {
        let that = this as Base2dMeshItem;
        let width = (that.config && that.config.width) ? that.config.width.value : useConfigStore().d3Config.camera.near;
        let height = (that.config && that.config.height) ? that.config.height.value : useConfigStore().d3Config.camera.near;
        return new THREE.PlaneGeometry(width,height, 132);
    }

    private _material() {
        let that = this as Base2dMeshItem;
        if (!that.config) {
            return null;
        }
        let color = (that.config && that.config.color) ? that.config.color.value : useConfigStore().d3Config.mesh.line.color;
        let lineWidth = (that.config && that.config.size) ? that.config.size.value : useConfigStore().d3Config.mesh.line.width;
        if (that.config && that.config.currentMaterial && that.config.currentMaterial.value) {
            return new that.config.currentMaterial.value({
                color: color,
                lineWidth: lineWidth
            });
        } else {
            return new THREE.MeshBasicMaterial({
                color: color,
                lineWidth: lineWidth
            });
        }
    }
    protected _mouseMove(point: THREE.Vector3, mesh: THREE.Mesh,event:MouseEvent) {
        if(!mesh){
            return;
        }
        point.z += 0.1;
        mesh.position.copy(point);
    }
}

2.5. 基础绘制

  1. ThreeModel.ts
javascript 复制代码
import * as THREE from "three"
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import * as TWEEN from 'three/examples/jsm/libs/tween.module';
import { useStateStore } from "@/stores/state";
import { OprationModes } from "@/model/OprationModes";
import { COMMOND_TYPE_D3_HELP, COMMOND_TYPE_D3_OP, COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE } from "@/common/Constant";
import type { ToolBarItem } from "@/model/ToolItem/ToolBartem";
import { useToolStore } from "@/stores/tool";
import type { D3Config, FillType } from "@/model/D3Config";
import { HandWritingControl } from "../control/HandWritingControl";
import { EraserControl } from "../control/EraserControl";
import { D2Control } from "../control/D2Control";
import { D3Control } from "../control/D3Control";
import { GlobalEventControl } from "../control/GlobalEventControl";
import { useConfigStore } from "@/stores/config";
import { ElMessage } from "element-plus";
import EventBus from "@/utils/EventBus";
import EraserMesh from "../mesh/eraserMesh";
import HandwritingMesh from "../mesh/HandwritingMesh";
import type { ToolBarMeshItem } from "@/model/ToolItem/ToolBarMeshItem";
import D2Mesh from "../mesh/D2Mesh";
import D3Mesh from "../mesh/D3Mesh";
/**
 * 全局的三维的基础内容
 */
export class ThreeMode {
    //缓动的计算工具
    easingFunction = TWEEN.Easing.Quadratic.Out;
    //纹理的加载器
    textureLoader = new THREE.TextureLoader();
    //渲染器
    renderer: THREE.WebGLRenderer;
    //摄像头
    camera: THREE.PerspectiveCamera;
    //场景
    scene: THREE.Scene;
    //环境光
    light: THREE.Light;
    //视角控制器
    control: OrbitControls;

    //坐标轴的辅助对象
    axesHelper: OrbitControls;

    //摄像机的辅助对象
    cameraHelper: OrbitControls;

    //所有创建的三维对象信息
    objects: THREE.Object3D[] = [];

    //手写板的实例
    handWritingMeshInstance: HandWritingControl | null = null;

    //黑板擦的实例
    eraserMeshInstance: EraserControl | null = null;

    //二维图形的实例
    d2MeshInstance: D2Control | null = null;

    //三维图形的实例
    d3MeshInstance: D3Control | null = null;

    //三维图形的实例
    globalEventInstance: GlobalEventControl | null = null;

    constructor() {
    }

    /**
     * 初始化三维的常见服务
     * @param root 
     */
    public init(root: Element, d3Config: D3Config) {
        this.renderer = new THREE.WebGLRenderer({
            antialias: true
        });
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(window.innerWidth, window.innerHeight);

        this.camera = new THREE.PerspectiveCamera(d3Config.camera.fov, window.innerWidth / window.innerHeight, d3Config.camera.near, d3Config.camera.far)
        // camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 1000 );
        this.camera.zoom = d3Config.camera.zoom;
        this.camera.position.copy(d3Config.camera.position);

        this.scene = new THREE.Scene();
        if (d3Config.scene.texture) {
            this.scene.background = d3Config.scene.texture;
        } else {
            this.scene.background = new THREE.Color(d3Config.scene.background);
        }
        this.scene.backgroundBlurriness = d3Config.scene.backgroundBlurriness;
        this.scene.environment = d3Config.scene.environment;

        //灯光
        this.light = new THREE.AmbientLight({
            color: d3Config.light.color,
            intensity: d3Config.light.intensity
        });
        this.scene.add(this.light);

        // {
        //     const geometry = new THREE.CircleGeometry(1,32);
        //     const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
        //     const cube = new THREE.Mesh(geometry, material);
        //     this.scene.add(cube);
        // }
        this.control = new OrbitControls(this.camera, this.renderer.domElement);
        this.control.enableDamping = true;
        this.control.dampingFactor = 0.05;
        this.control.enabled = false;
        // 监听change事件
        this.control.addEventListener('change', () => {
            d3Config.camera.position.copy(this.camera.position);
            if (useStateStore().currentMode == OprationModes.D3 && this.d3MeshInstance) {
                this.d3MeshInstance.updatePlanPostion(this.camera);
            }
        });
        // 监听start事件
        this.control.addEventListener('start', () => {
            d3Config.camera.position.copy(this.camera.position);
        });
        // 监听end事件
        this.control.addEventListener('end', () => {
            d3Config.camera.position.copy(this.camera.position);
            if (useStateStore().currentMode == OprationModes.D3 && this.d3MeshInstance) {
                this.d3MeshInstance.updatePlanPostion(this.camera);
            }
        });

        this.cameraHelper = new THREE.CameraHelper(this.camera);
        this.axesHelper = new THREE.AxesHelper(d3Config.camera.far);
        useToolStore().tools.bottom.forEach((item: ToolBarItem) => {
            if (item.commond === COMMOND_TYPE_D3_HELP && item.selected) {
                switch (item.key) {
                    case 'OrbitControls'://轨道控制器(OrbitControls)
                        this.control.enabled = true;
                        break;
                    case 'CameraHelper'://模拟相机视锥体的辅助对象
                        this.scene.add(this.cameraHelper);
                        break;
                    case 'AxesHelper'://坐标轴
                        this.scene.add(this.axesHelper);
                        break;
                    default:
                        break;
                }
            }
        })
        root.appendChild(this.renderer.domElement);

        this.animate();

        //全局事件的监听
        this.globalEventInstance = new GlobalEventControl(this.renderer.domElement, this);
        this.globalEventInstance.init();
    }

    //销毁实例信息
    public dispose() {
        if (this.control) {
            this.control.dispose();
        }
        if (this.cameraHelper) {
            this.cameraHelper.dispose();
        }
        if (this.axesHelper) {
            this.axesHelper.dispose();
        }

        //全局事件的监听
        if (this.handWritingMeshInstance) {
            this.handWritingMeshInstance.dispose();
        }
        if (this.eraserMeshInstance) {
            this.eraserMeshInstance.dispose();
        }
        if (this.globalEventInstance) {
            this.globalEventInstance.dispose();
        }
        if (this.d2MeshInstance) {
            this.d2MeshInstance.dispose();
        }
        if (this.d3MeshInstance) {
            this.d3MeshInstance.dispose();
        }

        if (this.renderer) {
            this.renderer.clear();
            this.renderer.dispose();
        }
    }


    /**
     * 判断是否需要重新计算大小尺寸
     * @param renderer 
     * @returns 
     */
    public resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
        const canvas = renderer.domElement
        const width = canvas.clientWidth
        const height = canvas.clientHeight
        const needResize = canvas.width !== width || canvas.height !== height
        if (needResize) {
            renderer.setSize(width, height, false)
        }

        return needResize
    }


    /**
     * 默认的动画
     */
    public animate() {
        if(!this.renderer){
            return;
        }
        if (this.resizeRendererToDisplaySize(this.renderer)) {
            const canvas = this.renderer.domElement
            this.camera.aspect = canvas.clientWidth / canvas.clientHeight
            this.camera.updateProjectionMatrix()
        }

        TWEEN.update();

        this.control.update();
        this.renderer.render(this.scene, this.camera);

        requestAnimationFrame(this.animate.bind(this));
    }
    /**
     * 获取所有的物体信息
     * @returns 
     */
    public getObjects() {
        let result: THREE.Mesh = [];
        if (useStateStore().currentMode == OprationModes.D3 && this.d3MeshInstance) {
            let meshs = this.d3MeshInstance.getChildren();
            result.push(...meshs);
        } else if (useStateStore().currentMode == OprationModes.D2 && this.d2MeshInstance) {
            let meshs = this.d2MeshInstance.getChildren();
            result.push(...meshs);
        }
        return result;
    }
    /**
     * 设置场景的背景颜色
     * @param background 
     */
    public setBackground(background: string) {
        this.scene.background = new THREE.Color(background);
    }

    /**
     * 设置背景模糊度
     * @param newVal 
     */
    public setBackgroundBlurriness(newVal: number) {
        this.scene.backgroundBlurriness = newVal;
    }

    /**
     * 设置场景的纹理
     * @param newVal 
     */
    public setSceneTexture(newVal: number) {
        this.scene.background = this.textureLoader.load(newVal);
    }

    /**
     * 设置场景的环境纹理
     * @param newVal 
     */
    public setSceneEnvironment(newVal: number) {
        this.scene.environment = this.textureLoader.load(newVal);
    }

    /**
     * 设置灯光颜色
     * @param newVal 
     */
    public setLightColor(newVal: THREE.Color) {
        this.light.color = newVal;
    }
    /**
     * 设置灯光强度
     * @param newVal 
     */
    public setLightIntensity(newVal: number) {
        this.light.intensity = newVal;
    }


    /**
     * 设置摄像头的视角
     * @param fov 
     */
    public setCameraFov(fov: number) {
        this.camera.fov = fov;
    }

    /**
     * 设置摄像头的近端面
     * @param near 
     */
    public setCameraNear(near: number) {
        this.camera.near = near;
    }

    /**
     * 设置摄像头的远端面
     * @param near 
     */
    public setCameraFar(far: number) {
        this.camera.far = far;
        this.axesHelper.size = far;
    }

    /**
     * 设置摄像头的缩放比例
     * @param zoom 
     */
    public setCameraZoom(zoom: number) {
        this.camera.zoom = zoom;
    }

    /**
     * 设置摄像头的位置信息
     * @param zoom 
     */
    public setCameraPosition(position: THREE.Vector3) {
        this.camera.position.copy(position);
    }


    /**
     * 设置是否禁用辅助工具
     * @param zoom 
     */
    public setControlEnabled(enabled: boolean) {
        this.control.enabled = enabled;
    }

    /**
     * 设置摄像头的辅助对象
     * @param zoom 
     */
    public setCameraHelper(enabled: boolean) {
        this.cameraHelper.removeFromParent();
        if (enabled) {
            this.scene.add(this.cameraHelper);
        }
    }
    /**
     * 设置坐标轴的辅助对象
     * @param zoom 
     */
    public setAxesHelper(enabled: boolean) {
        this.axesHelper.removeFromParent();
        if (enabled) {
            this.scene.add(this.axesHelper);
        }
    }
    /**
     * 清空对象
     */
    public clear() {
        if (useStateStore().currentMode == OprationModes.D3) {
            this.objects.forEach((item) => {
                item.removeFromParent();
            })
        } else if (useStateStore().currentMode == OprationModes.D2) {
            if (!this.d2MeshInstance) {
                return;
            }
            this.d2MeshInstance.clear();
        } else {
            if (!this.handWritingMeshInstance) {
                return;
            }
            this.handWritingMeshInstance.clear();
        }
    }
    /**
     * 放大场景
     * @param value 放大的值,不传时自动匹配 
     */
    public add(value?: number | undefined) {
        if (value === undefined) {
            this.camera.position.z--;
        } else {
            this.camera.position.z -= value;
        }
    }
    /**
     * 缩小场景
     * @param value 缩小的值,不传时自动匹配 
     */
    public sun(value?: number | undefined) {
        if (value === undefined) {
            this.camera.position.z++;
        } else {
            this.camera.position.z += value;
        }
    }
    /**
     * 场景复位
     */
    public reset() {
        this.toPosition(useConfigStore().d3Config.camera.position, new THREE.Vector3(0, 0, 0));
    }

    /**
     * 视角转到指定为止
     * @param targetPosition 目标位置
     * @param targetLookAt 目标视角
     * @param animationDuration 动画事件
     */
    public toPosition(targetPosition: THREE.Vector3, targetLookAt: THREE.Vector3, animationDuration: number = 800) {
        // 停止当前所有动画
        TWEEN.removeAll();

        // 创建位置动画
        new TWEEN.Tween(this.camera.position)
            .to(targetPosition, animationDuration)
            .easing(this.easingFunction)
            .onComplete(() => {
                this.camera.position.set(targetPosition.x, targetPosition.y, targetPosition.z);
            })
            .start();
        new TWEEN.Tween(this.control.target)
            .to(targetLookAt, animationDuration)
            .easing(this.easingFunction)
            .onComplete(() => {
                this.control.target.set(targetLookAt.x, targetLookAt.y, targetLookAt.z);
            })
            .start();
    }

    /**
     * 添加元素到常见当中
     * @param obj 要添加的元素信息,可以是多个
     * @returns 
     */
    public addObject(...obj: THREE.Object3D) {
        if (!obj) {
            return;
        }
        obj.forEach((item: THREE.Object3D) => {
            this.scene.add(item);
        })
    }



    /**
     * 开启手写模式
     */
    public starThandWriting() {
        //停用轨道控制器,避免事件冲突
        EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, false);

        if (!this.handWritingMeshInstance) {
            let width = this.camera.getFilmWidth();
            let height = this.camera.getFilmHeight();
            const geometry = new THREE.PlaneGeometry(width, height);
            const material = new THREE.MeshBasicMaterial({
                color: useConfigStore().d3Config.handWriting.planeColor,
                transparent: true,
                opacity: useConfigStore().d3Config.handWriting.planeOpacity,
            });
            let mesh = new HandwritingMesh(geometry, material, new THREE.LineBasicMaterial({
                color: useConfigStore().d3Config.handWriting.line.color,
                linewidth: useConfigStore().d3Config.handWriting.line.width
            }));
            this.handWritingMeshInstance = new HandWritingControl(this.renderer.domElement, this, mesh);
        }
        this.addObject(this.handWritingMeshInstance.mesh);

        //初始化手写板
        this.handWritingMeshInstance.init();

        this.setControlEnabled(false);

        useStateStore().cursor = "url(/images/cricle.png), auto";

        //重置视角
        this.reset();
    }

    /**
     * 关闭手写模式
     */
    public stopThandWriting() {
        if (!this.handWritingMeshInstance) {
            return;
        }
        //把手写板移除调
        this.handWritingMeshInstance.removeFromParent();

        //销毁事件
        this.handWritingMeshInstance.dispose();

        //停用轨道控制器,避免事件冲突
        EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, true);

        //关闭橡皮擦
        let eraserItem = useToolStore().getItemByKeyAndCommond('eraser', COMMOND_TYPE_D3_OP);
        if (eraserItem) {
            eraserItem.selected = true;
            EventBus.emit(COMMOND_TYPE_D3_OP, eraserItem);
        }
    }

    /**
     * 开启橡皮擦
     */
    public startEraser() {
        if (!this.handWritingMeshInstance) {
            ElMessage.warning("请先开启手写模式~");
            return;
        }
        //停用轨道控制器,避免事件冲突
        EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, false);

        if (!this.eraserMeshInstance) {
            let mesh = new EraserMesh(
                new THREE.CircleGeometry(useConfigStore().d3Config.eraser.radius, 132),
                new THREE.ShaderMaterial({
                    // color: useConfigStore().d3Config.eraser.color,
                    uniforms: {
                        centerColor: { value: useConfigStore().d3Config.eraser.color },
                        edgeColor: { value: useConfigStore().d3Config.eraser.color }
                    },
                    vertexShader: `
                        varying vec2 vUv;
                        void main() {
                        vUv = uv;
                            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                        }
                    `,
                    fragmentShader: `
                        uniform vec3 centerColor;
                        uniform vec3 edgeColor;
                        varying vec2 vUv;
                        void main() {
                            float distance = length(vUv - vec2(0.5, 0.5));
                            vec3 color = mix(centerColor, edgeColor, distance);
                            float alpha = 1.0 - distance * 1.9; // 渐变透明度
                            gl_FragColor = vec4(color, alpha);
                        }
                    `,
                    transparent: true // 启用透明度混合
                })
            );
            // if (!this.handWritingMeshInstance) {
            //     this.starThandWriting();
            // }
            this.eraserMeshInstance = new EraserControl(this.renderer.domElement, this, mesh, this.handWritingMeshInstance.mesh);
        }
        this.addObject(this.eraserMeshInstance.mesh);

        //初始化手写板
        this.eraserMeshInstance.init();

        useStateStore().cursor = "grab";
        
        this.setControlEnabled(false);

        //销毁事件
        this.handWritingMeshInstance.dispose();
    }
    /**
     * 停止橡皮擦
     */
    public stopEraser() {
        if (!this.eraserMeshInstance) {
            return;
        }
        //把橡皮擦移除调
        this.eraserMeshInstance.removeFromParent();

        //销毁事件
        this.eraserMeshInstance.dispose();

        let handWritingItem = useToolStore().getItemByKey('write');
        if ((handWritingItem && handWritingItem.selected)) {//开启手写板状态的时候恢复鼠标形状
            useStateStore().cursor = "url(/images/cricle.png), auto";
            //初始化手写板
            if (this.handWritingMeshInstance) {
                this.handWritingMeshInstance.init();
            }
        } else {
            //停用轨道控制器,避免事件冲突
            EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, true);
        }
    }
    /**
     * 开始绘制二维物体
     * @param newVal 新的值
     */
    public startMesh2D(item?: ToolBarMeshItem | null) {
        //停用轨道控制器,避免事件冲突
        EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, false);

        if (!this.d2MeshInstance) {
            let width = this.camera.getFilmWidth();
            let height = this.camera.getFilmHeight();
            const geometry = new THREE.PlaneGeometry(width, height);
            const material = new THREE.MeshBasicMaterial({
                color: useConfigStore().d3Config.mesh.d2PlaneColor,
                transparent: true,
                opacity: useConfigStore().d3Config.handWriting.planeOpacity
            });

            this.d2MeshInstance = new D2Control(this.renderer.domElement, this, new D2Mesh(geometry, material, item));
        }
        this.addObject(this.d2MeshInstance?.mesh);
        if (item) {
            this.d2MeshInstance.mesh.item = item;
        }
        //初始化手写板
        this.d2MeshInstance.init();

        //重置视角
        this.reset();
    }

    /**
     * 结束绘制二维物体
     * @param newVal 新的值
     */
    public stopMesh2D() {
        //停用轨道控制器,避免事件冲突
        EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, true);

        if (this.d2MeshInstance && this.d2MeshInstance != null) {
            (this.d2MeshInstance).removeFromParent();
            // d2MeshInstance = null;
            this.d2MeshInstance.dispose();
        }
    }


    /**
     * 暂停绘制二维物体
     * @param newVal 新的值
     */
    public pauseMesh2D() {
        if (!this.d2MeshInstance) {
            return;
        }
        this.d2MeshInstance.dispose();
    }


    /**
     * 板擦的半径的变动
     * @param newVal 新的值
     */
    public startMesh3D(item: ToolBarMeshItem | null) {
        //启用轨道控制器,避免事件冲突
        // EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, false);
        this.setControlEnabled(false);

        let width = this.camera.getFilmWidth();
        let height = this.camera.getFilmHeight();
        let deep = this.camera.getFocalLength();
        if (!this.d3MeshInstance) {
            const geometry = new THREE.BoxGeometry(width, height, deep);
            const material = new THREE.MeshBasicMaterial({
                color: useConfigStore().d3Config.mesh.d2PlaneColor,
                transparent: true,
                opacity: 0.0001
            });
            this.d3MeshInstance = new D3Control(this.renderer.domElement, this, new D3Mesh(geometry, material, item));
        }

        this.d3MeshInstance.updatePlanPostion(this.camera);

        this.addObject(this.d3MeshInstance.mesh);
        if (item) {
            this.d3MeshInstance.mesh.item = item;
        }
        //初始化手写板
        this.d3MeshInstance.init();
    }
    /**
     * 结束绘制三维物体
     * @param newVal 新的值
     */
    public stopMesh3D() {
        if (!this.d3MeshInstance) {
            return;
        }
        //停用轨道控制器,避免事件冲突
        // EventBus.emit(COMMOND_TYPE_SELF_WRITE_STATUS_CHANGE, true);

        if (this.d3MeshInstance && this.d3MeshInstance != null) {
            this.d3MeshInstance.removeFromParent();
            // d2MeshInstance = null;
        }

        this.d3MeshInstance.dispose();
    }
    /**
    * 暂停绘制二维物体
    * @param newVal 新的值
    */
    public pauseMesh3D() {
        if (!this.d3MeshInstance) {
            return;
        }
        this.d3MeshInstance.dispose();
    }

    public setHandWritingPlaneColor(color: THREE.color) {
        if (this.handWritingMeshInstance) {
            this.handWritingMeshInstance.setHandWritingPlaneColor(color);
        }
    }
    public setHandWritingPlaneOpacity(value: number) {
        if (this.handWritingMeshInstance) {
            this.handWritingMeshInstance.setHandWritingPlaneOpacity(value);
        }
    }
    public setHandWritingPlaneTexture(texture: THREE.Texture) {
        if (this.handWritingMeshInstance) {
            this.handWritingMeshInstance.setHandWritingPlaneTexture(this.textureLoader, texture);
        }
    }
    public setHandWritingLineColor(color: THREE.color) {
        if (this.handWritingMeshInstance) {
            this.handWritingMeshInstance.setHandWritingLineColor(color);
        }
    }
    public setHandWritingLineWidth(value: number) {
        if (this.handWritingMeshInstance) {
            this.handWritingMeshInstance.setHandWritingLineWidth(value);
        }
    }
    public setEraserColor(color: THREE.color) {
        if (this.eraserMeshInstance) {
            this.eraserMeshInstance.setEraserColor(color);
        }
    }
    public setEraserRadius(value: number) {
        if (this.eraserMeshInstance) {
            this.eraserMeshInstance.setEraserRadius(value);
        }
    }
    public setMeshD2PlanColor(color: THREE.color) {
        if (this.d2MeshInstance) {
            this.d2MeshInstance.setMeshD2PlanColor(color);
        }
    }
    public setMeshD2PlanOpacity(value: number) {
        if (this.d2MeshInstance) {
            this.d2MeshInstance.setMeshD2PlanOpacity(value);
        }
    }
    public setMeshD2PlanTexture(texture: THREE.Texture) {
        if (this.d2MeshInstance) {
            this.d2MeshInstance.setMeshD2PlanTexture(this.textureLoader, texture);
        }
    }
    public setMeshLineColor(color: THREE.color) {
        if (this.d2MeshInstance) {
            this.d2MeshInstance.setMeshLineColor(color);
        }
    }
    public setMeshLineWidth(value: number) {
        if (this.d2MeshInstance) {
            this.d2MeshInstance.setMeshLineWidth(value);
        }
    }
    public setMeshFillType(value: FillType) {
        if (this.d2MeshInstance) {
            this.d2MeshInstance.setMeshFillType(value);
        }
    }
    public setMeshFillColor(color: THREE.color) {
        if (this.d2MeshInstance) {
            this.d2MeshInstance.setMeshFillColor(color);
        }
    }

}
  1. BaseControl.ts
javascript 复制代码
import * as THREE from "three"
import type { ThreeMode } from "../model/ThreeModel";
import { useStateStore } from "@/stores/state";
import type { MouseStatus } from "@/model/MouseStatus";

export abstract class BaseControl<T extends THREE.Mesh>{
    //事件的dom元素
    protected ele: Element;
    
    //射线,用于获取鼠标点击之后的三维坐标信息
    protected raycaster: THREE.Raycaster = new THREE.Raycaster();

    //手写的状态
    protected currentState: boolean = false;

    //三维实例
    protected threeMode:ThreeMode;

    //当前操作的物体
    public mesh:T;

    private _mousedown = this.mousedown.bind(this);
    private _mousemove = this.mousemove.bind(this);
    private _mouseup = this.mouseup.bind(this);
    private _contextmenu = this.contextmenu.bind(this);

    constructor(ele:Element,threeMode:ThreeMode,mesh:T){
        this.ele = ele;
        this.threeMode = threeMode;
        this.mesh = mesh;

        this. _mousedown = this.mousedown.bind(this);
        this. _mousemove = this.mousemove.bind(this);
        this. _mouseup = this.mouseup.bind(this);
        this. _contextmenu = this.contextmenu.bind(this);
    }
    /**
     * 初始化
     * @param ele 
     * @param mesh 
     */
    public init(){
        this.ele.addEventListener('mousedown', this._mousedown, { passive: false });
        this.ele.addEventListener('mousemove', this._mousemove);
        this.ele.addEventListener('mouseup', this._mouseup);
        this.ele.addEventListener('contextmenu', this._contextmenu);
    } ;

    /**
     * 销毁
     * @returns 
     */
    public  dispose(){
        if (!this.ele) {
            return;
        }
        this.currentState = false;
        if(this.mesh){
            this.mesh.currentMesh = null;
        }
        this.ele.removeEventListener('mousedown', this._mousedown);
        this.ele.removeEventListener('mousemove', this._mousemove);
        this.ele.removeEventListener('mouseup', this._mouseup);
        this.ele.removeEventListener('contextmenu', this._contextmenu);
    };

    protected getMousePoint(objects:THREE.Mesh[],mouseStatus:MouseStatus|null){
        const mouse = new THREE.Vector2();
        mouse.x = ((event as MouseEvent).clientX / window.innerWidth) * 2 - 1;
        mouse.y = -((event as MouseEvent).clientY / window.innerHeight) * 2 + 1;

        // console.log(this,this.mesh.plane);
        this.raycaster.setFromCamera(mouse, this.threeMode.camera);
        let _objects = [...objects];
        if(this.mesh){
            if(this.mesh && this.mesh.plane){
                _objects.push(this.mesh.plane);
            }
        }
        const intersects = this.raycaster.intersectObjects(_objects, false);
        // if (intersects.length > 0) {//创建黑板擦,并添加到场景中去
        //     const point = intersects[0].point;
        //     //创建新的物体信息
        //     useStateStore().currentMesh =  this.mesh.create(point);

        //     EventBus.emit(COMMOND_TYPE_MESH_SELECTED,(useStateStore().currentMesh as D2Mesh).item);
        // }
        if(mouseStatus != null){
            useStateStore().currentStatus = mouseStatus as MouseStatus;
        }

        return intersects;
    }
    public removeFromParent() {
        if(this.mesh){
            this.mesh.removeFromParent();
        }
    }

    /**
     * 鼠标按下事件
     * @param event 
     */
    protected abstract mousedown(event: Event) :void;
    /**
     * 鼠标抬起事件
     * @param event 
     */
    protected abstract mouseup(event: Event) :void;
    /**
     * 鼠标移动事件
     * @param event 
     */
    protected abstract mousemove(event: Event) :void;

    /**
     * 右键事件
     * @param event 
     */
    protected contextmenu(event: Event){
        event.preventDefault();
    }
}

三、手写板基础功能

3.1 物体定义

  1. HandwritingMesh.ts
javascript 复制代码
import * as THREE from "three"
import HandwritingLineMesh from "./HandwritingLineMesh";

/**
 * 手写的物体对象
 */
export default class HandwritingMesh extends THREE.Group {
    //绘制的平面
    _plane: THREE.Mesh;
    //手写的线
    _lines: HandwritingLineMesh[] = [];
    //手写线的材质信息
    _lineMaterial: THREE.LineBasicMaterial;
    //当前正在手写的线条
    _currentLine:HandwritingLineMesh|null = null;

    constructor(planGeometry: THREE.PlaneGeometry, planMaterial: THREE.Material, lineMaterial: THREE.LineBasicMaterial) {
        super();
        this._plane = new THREE.Mesh(planGeometry, planMaterial);
        super.add(this._plane);

        this._lineMaterial = lineMaterial;
    }
    
    /**
     * 清楚指定范围内容的手写内容
     * @param point 
     * @param radius 
     */
    public removeByPoint(point: THREE.Vector3, radius: number) {
        let allNewLinePoints:HandwritingLineMesh[] = [];
        for (let i = 0; i < this.lines.length; i++) {
            let item = this.lines[i];
            if(item){
                let newLinePoints = item.removeByPoint(point,radius);
                if(newLinePoints.length>0){
                    newLinePoints.forEach(newPoint=>{
                        if(newPoint.length<=0){
                            return;
                        }
                        let geometry = new THREE.BufferGeometry();
                        let material = new THREE.LineBasicMaterial();

                        geometry.copy((item as THREE.Line).geometry);
                        material.copy((item as THREE.Line).material);

                        let newLine = new HandwritingLineMesh(geometry,material);
                        newLine.addPoint(...newPoint)
                        allNewLinePoints.push(newLine);
                    })

                    //移除旧的
                    item.removeFromParent();
                    this.lines.splice(i,1);
                    i--;
                }
            }
        }
        if(allNewLinePoints.length>0){
            allNewLinePoints.forEach(item=>{
                this.addLine(item);
            })
        }
    }
    /**
     * 清空手写内容
     */
    public clear() {
        this.lines.forEach(item=>{
            item.removeFromParent();
        });
        this.lines.length = 0;
    }

    /**
     * 新建手写线条
     * @param geometry 
     */
    public createLine(geometry?:THREE.BufferGeometry): HandwritingLineMesh {
        if(!geometry){
            geometry = new THREE.BufferGeometry();
        }
        return new HandwritingLineMesh(geometry,this._lineMaterial);
    }

    /**
     * 添加线信息
     * @param line 
     */
    public addLine(line: HandwritingLineMesh) {
        this._plane.add(line);
        this.lines.push(line);
        this.currentLine = line;
    }

    /**
     * 添加线信息
     * @param line 
     */
    public addPoint(point: THREE.Vector3) {
        if(this._currentLine){
            this._currentLine.addPoint(point);
        }
    }
    
    /**
     * 开始手写
     * @param point 
     */
    public startWriting(point:THREE.Vector3){
        let line = this.createLine();
        line.addPoint(point);
        this.addLine(line);
    }

    /**
     * 停止手写
     */
    public stopWriting(){
        this._currentLine = null;
    }

    /**
     * 从上级移除
     */
    public removeFromParent() {
        super.removeFromParent();
    }

    set plane(plane: THREE.Mesh) {
        this._plane = plane;
    }
    get plane(): THREE.Mesh {
        return this._plane;
    }

    set lines(lines: HandwritingLineMesh[]) {
        this._lines = lines;
    }
    get lines(): HandwritingLineMesh[] {
        return this._lines;
    }
    
    set lineMaterial(lineMaterial: THREE.LineBasicMaterial) {
        this._lineMaterial = lineMaterial;
    }
    get lineMaterial(): THREE.LineBasicMaterial {
        return this._lineMaterial;
    }

    set currentLine(currentLine: HandwritingLineMesh) {
        this._currentLine = currentLine;
    }
    get currentLine(): HandwritingLineMesh|null {
        return this._currentLine;
    }
}
  1. HandwritingLineMesh.ts
javascript 复制代码
import * as THREE from "three"

/**
 * 手写的线条对象
 */
export default class HandwritingLineMesh extends THREE.Line{
    //手写的移动轨迹
    _points:number[] = [];

    constructor(geometry:THREE.BufferGeometry,material:THREE.LineBasicMaterial) {
        super( geometry, material );
    }

    /**
     * 添加线上的点
     * @param line 
     */
    public addPoint(...point:THREE.Vector3){
        if(!point){
            return;
        }
        point.forEach((item: THREE.Vector3)=>{
            this.points.push(item.x,item.y,item.z);
        });
        (this as THREE.Line).geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( this._points, 3 ) );
    }
    /**
     * 清楚指定范围内容的手写内容
     * 
     * [1,2,3 , 2,2,1 , 23,2,3 , 334,23,32]
     * 
     * @param point 
     * @param radius 
     */
    public removeByPoint(point: THREE.Vector3, radius: number):THREE.Vector3[] {
        let resultPositions:THREE.Vector3[] = [];

        let positions:THREE.Vector3[] = [];
        for (let i = 0; i < this.points.length; i =i+3) {
            positions.push(new THREE.Vector3(this.points[i],this.points[i+1],this.points[i+2]));
        }
        let newLinePoints:THREE.Vector3[] = [];
        resultPositions.push(newLinePoints);
        for (let i = 0; i < positions.length; i++) {
            if(positions[i].distanceTo(point) <= radius){
                positions.splice(i,3);
                i = i--;

                //截取的坐标点
                newLinePoints = [];
                resultPositions.push(newLinePoints);
            }else{
                newLinePoints.push(positions[i]);
            }
        }

        // this.geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( this._points, 3 ) );
        return resultPositions;
    }

    /**
     * 从上级移除
     */
    public removeFromParent() {
        super.removeFromParent();
    }

    set points(points:number[]){
        this._points = points;
    }
    get points():number[]{
        return this._points ;
    }
}
  1. EraserMesh.ts
javascript 复制代码
import * as THREE from "three"
/**
 * 橡皮擦的显示物体
 */
export default class EraserMesh extends THREE.Mesh{
    constructor(geometry:THREE.CircleGeometry,material:THREE.MeshBasicMaterial) {
        super( geometry, material );
    }
    /**
     * 从上级移除
     */
    public removeFromParent() {
        super.removeFromParent();
    }
    /**
     * 移动到指定为止
     * @param point 指定的位置信息
     */
    public move(point: THREE.Vector3) {
        // this.geometry.setAttribute("position",new THREE.Float32BufferAttribute(point,));
        (this as THREE.Mesh).position.copy(point);
    }
    /**
     * 设置物体的颜色
     * @param newVal 新的颜色值
     */
    public setColor(newVal: THREE.Color) {
        // this.material.color.copy(newVal);
        (this as THREE.Mesh).material.uniforms.centerColor.value.copy(newVal);
        (this as THREE.Mesh).material.uniforms.edgeColor.value.copy(newVal);
        // uniforms: {
        //     centerColor: { value: useConfigStore().d3Config.eraser.color },
        //     edgeColor: { value: useConfigStore().d3Config.eraser.color }
        // }
    }
    /**
     * 设置物体的大小
     * @param newVal 新的半径
     */
    public setRadius(newVal: number) {
        (this as THREE.Mesh).geometry = new THREE.CircleGeometry(newVal,132);//.parameters.radius = newVal;
    }
}

3.2 逻辑控制

  1. GlobalEventControl.ts
javascript 复制代码
import { BaseControl } from "./BaseControl";
import * as THREE from "three"
import { useStateStore } from "@/stores/state";
import { MouseStatus } from "@/model/MouseStatus";
import EventBus from "@/utils/EventBus";
import { COMMOND_TYPE_MESH_CANCEL_SELECTED, COMMOND_TYPE_MESH_SELECTED } from "@/common/Constant";
import type { ThreeMode } from "../model/ThreeModel";
import { OprationModes } from "@/model/OprationModes";
import type D2Mesh from "../mesh/D2Mesh";
import type D3Mesh from "../mesh/D3Mesh";

export class GlobalEventControl extends BaseControl<THREE.Mesh> {

    //初始拖动的位置信息
    _startPoint: THREE.Vector3 | null;


    constructor(ele: Element, threeMode: ThreeMode) {
        super(ele, threeMode, null);
        this._startPoint = null;
    }

    /**
     * 鼠标按下的事件
     * @param event 事件值
     */
    protected mousedown(event: Event) {
        // 阻止窗口滚动行为
        event.preventDefault();

        //如果开启了轨道控制器,则不能手写
        if (useStateStore().currentStatus != MouseStatus.None) {
            return;
        }
        this.currentState = true;
        let objects = [...this.threeMode.getObjects()];
        if(useStateStore().currentMode == OprationModes.D2){
            if(this.threeMode.d2MeshInstance && this.threeMode.d2MeshInstance?.mesh){
                objects.push(this.threeMode.d2MeshInstance?.mesh.plane);
            }
        }else if(useStateStore().currentMode == OprationModes.D3){
            if(this.threeMode.d3MeshInstance && this.threeMode.d3MeshInstance?.mesh){
                objects.push(this.threeMode.d3MeshInstance?.mesh.plane);
            }
        }
        const intersects =  this.getMousePoint(objects,null);
        if (intersects.length > 0) {//创建黑板擦,并添加到场景中去
            
            if(useStateStore().currentMode == OprationModes.D2){
                if(this.threeMode.d2MeshInstance && this.threeMode.d2MeshInstance?.mesh){
                    if(intersects[0].object.id == this.threeMode.d2MeshInstance?.mesh.plane.id){
                        return;
                    }
                }
            }else if(useStateStore().currentMode == OprationModes.D3){
                if(this.threeMode.d3MeshInstance && this.threeMode.d3MeshInstance?.mesh){
                    if(intersects[0].object.id == this.threeMode.d3MeshInstance?.mesh.plane.id){
                        return;
                    }
                }
            }
            const point = intersects[0].point;
            this._startPoint = point;

            useStateStore().currentMesh = intersects[0].object;
            //触发选中事件
            if(useStateStore().currentMode == OprationModes.D2){
                EventBus.emit(COMMOND_TYPE_MESH_SELECTED,(useStateStore().currentMesh as D2Mesh).item);
            }else if(useStateStore().currentMode == OprationModes.D3){
                EventBus.emit(COMMOND_TYPE_MESH_SELECTED,(useStateStore().currentMesh as D3Mesh).item);
            }

            useStateStore().cursor = "move";
        } else {
            //触发取消选中事件
            EventBus.emit(COMMOND_TYPE_MESH_CANCEL_SELECTED, null);
            useStateStore().currentMesh = null;
        }
    }

    /**
     * 鼠标抬起的事件
     * @param event 事件值
     */
    protected mouseup(event: Event) {
        if(this.currentState && OprationModes.HandWriting !== useStateStore().currentMode){
            useStateStore().cursor = "default";
        }
        this.currentState = false;
        this._startPoint = null;
        //触发取消选中事件
        // if(useStateStore().currentStatus === MouseStatus.None && useStateStore().currentMesh){
        //     EventBus.emit(COMMOND_TYPE_MESH_CANCEL_SELECTED,(useStateStore().currentMesh as D2Mesh).item);
        //     useStateStore().currentMesh = null;
        // }
    }

    /**
     * 鼠标移动的事件
     * @param event 事件值
     */
    protected mousemove(event: Event) {
        if (!this.currentState || useStateStore().currentStatus != MouseStatus.None || !useStateStore().currentMesh) {
            return;
        }
        const intersects =  this.getMousePoint(this.threeMode.getObjects(),null);
        if (intersects.length > 0) {
            const point = intersects[0].point;

            if (this._startPoint && useStateStore().currentMesh) {
                /**
                 * 计算坐标的差额,并将选中的图形移动对应的距离
                 */
                let x = (point.x - this._startPoint.x);
                let y = (point.y - this._startPoint.y);
                let z = (point.z - this._startPoint.z);

                (useStateStore().currentMesh as THREE.Mesh).geometry.translate(x, y, 0);

                this._startPoint = point;
            }
        }
    }

}
  1. EraserControl.ts
javascript 复制代码
import { MouseStatus } from "@/model/MouseStatus";
import type EraserMesh from "../mesh/eraserMesh";
import type HandwritingMesh from "../mesh/HandwritingMesh";
import type { ThreeMode } from "../model/ThreeModel";
import { BaseControl } from "./BaseControl";
import * as THREE from "three"
import { useStateStore } from "@/stores/state";

export class EraserControl extends BaseControl<EraserMesh>{
    //手写板的实例
     handWritingMeshInstance: HandwritingMesh;

     constructor(ele: Element,threeMode:ThreeMode,mesh:EraserMesh,handWritingMeshInstance:HandwritingMesh){
        super(ele,threeMode,mesh);
        this.handWritingMeshInstance = handWritingMeshInstance;
     }

    /**
     * 鼠标按下的事件
     * @param event 事件值
     */
    protected  mousedown  (event: Event)  {
        // 阻止窗口滚动行为
        event.preventDefault();

        //如果开启了轨道控制器,则不能手写
        if(this.threeMode.control.enabled){
            return;
        }

        this.currentState = true;
        const intersects =  this.getMousePoint([this.handWritingMeshInstance.plane],MouseStatus.Create);
        if (intersects.length > 0) {//创建黑板擦,并添加到场景中去
            //将板擦移动到对应的位置
            this.mesh.move(intersects[0].point);

            //计算并移除需要删除的手写内容
            this.handWritingMeshInstance.removeByPoint(intersects[0].point,(this.mesh as THREE.Mesh).geometry.parameters.radius);
        }
    }

    /**
     * 鼠标抬起的事件
     * @param event 事件值
     */
    protected  mouseup  (event: Event)  {
        this.currentState = false;
        useStateStore().currentStatus = MouseStatus.None;
    }

    /**
     * 鼠标移动的事件
     * @param event 事件值
     */
    protected mousemove (event: Event)  {
        if (!this.currentState) {
            return;
        }
        const intersects =  this.getMousePoint([this.handWritingMeshInstance.plane],MouseStatus.Create);
        if (intersects.length > 0) {
            const point = intersects[0].point;
            //将板擦移动到对应的位置
            this.mesh.move(point);

            //计算并移除需要删除的手写内容
            this.handWritingMeshInstance.removeByPoint(point,(this.mesh as THREE.Mesh).geometry.parameters.radius);
        }
    }
    /**
     * 板擦的颜色变动
     * @param newVal 新的值
     */
    public  setEraserColor(newVal: THREE.Color) {
        if (!this.mesh) {
            return;
        }
        this.mesh.setColor(newVal);
    }

    /**
     * 板擦的半径的变动
     * @param newVal 新的值
     */
    public  setEraserRadius(newVal: number) {
        if (!this.mesh) {
            return;
        }
        this.mesh.setRadius(newVal);
    }

}
  1. HandWritingControl.ts
javascript 复制代码
import { MouseStatus } from "@/model/MouseStatus";
import type HandwritingMesh from "../mesh/HandwritingMesh";
import type { ThreeMode } from "../model/ThreeModel";
import { BaseControl } from "./BaseControl";
import { useStateStore } from "@/stores/state";
import * as THREE from "three"

export class HandWritingControl extends BaseControl<HandwritingMesh> {
     constructor(ele: Element,threeMode:ThreeMode,mesh:HandwritingMesh){
        super(ele,threeMode,mesh);
     }
    /**
     * 鼠标按下的事件
     * @param event 事件值
     */
    protected mousedown(event: Event) {
        // 阻止窗口滚动行为
        event.preventDefault();

        //如果开启了轨道控制器,则不能手写
        if (this.threeMode.control.enabled) {
            return;
        }

        this.currentState = true;
        const intersects =  this.getMousePoint([],null);
        if (intersects.length > 0) {
            //开始一个新的线条
            const point = intersects[0].point;
            point.z += 0.1;
            this.mesh.startWriting(point);
        }
        useStateStore().currentStatus = MouseStatus.Create;
    }

    /**
     * 鼠标抬起的事件
     * @param event 事件值
     */
    protected mouseup(event: Event) {
        this.currentState = false;
        this.mesh.stopWriting();
        useStateStore().currentStatus = MouseStatus.None;
    }

    /**
     * 鼠标移动的事件
     * @param event 事件值
     */
    protected mousemove(event: Event) {
        if (!this.currentState) {
            return;
        }
        const intersects =  this.getMousePoint([],null);
        if (intersects.length > 0) {
            const point = intersects[0].point;
            point.z += 0.1;
            this.mesh.addPoint(point);
        }
    }

    
    public clear() {
        this.mesh.clear();
    }

    /**
     * 设置手写背景颜色
     * @param color 背景颜色
     * @returns 
     */
    public setHandWritingPlaneColor (color: THREE.color) {
        this.mesh.plane.material.color = color;
    }
    /**
     * 修改背景板的透明度
     * @param newVal 
     */
    public setHandWritingPlaneOpacity(newVal: number) {
        this.mesh.plane.material.opacity = newVal;
    }
    /**
     * 修改背景板的纹理
     * @param newVal 
     */
    public setHandWritingPlaneTexture(textureLoader:THREE.TextureLoader,newVal: string) {
        let material = new THREE.MeshBasicMaterial();
        material.copy(this.mesh.plane.material);
        material.map = textureLoader.load(newVal);
        this.mesh.plane.material = material;
    }
    
    /**
     * 设置手写线条的颜色
     * @param color 线条的颜色
     * @returns 
     */
    public setHandWritingLineColor (color: THREE.color)  {
        let lineMaterial = new THREE.LineBasicMaterial();
        lineMaterial.copy(this.mesh.lineMaterial);
        lineMaterial.color = color;
        this.mesh.lineMaterial = lineMaterial;

        if(this.mesh.currentLine){
            (this.mesh.currentLine as THREE.Mesh).material = lineMaterial;
        }
    }
    /**
     * 设置手写线条的尺寸
     * @param width 线条的尺寸
     * @returns 
     */
    public setHandWritingLineWidth (width: number)  {
        let lineMaterial = new THREE.LineBasicMaterial();
        lineMaterial.copy(this.mesh.lineMaterial);
        lineMaterial.linewidth = width;
        this.mesh.lineMaterial = lineMaterial;

        if(this.mesh.currentLine){
            (this.mesh.currentLine as THREE.Mesh).material = lineMaterial;
        }
    }
}

未完待续~~~~~~~~~~~~~~

相关推荐
维齐洛波奇特利(male)2 小时前
@Pointcut(“execution(* com.hdzx..*(..))“)切入点与aop 导致无限循环
java·开发语言
IT枫斗者2 小时前
构建具有执行功能的 AI Agent:基于工作记忆的任务规划与元认知监控架构
android·前端·vue.js·spring boot·后端·架构
hotlinhao2 小时前
Nginx rewrite last 与 redirect 的区别——Vue history 模式短链接踩坑记录
前端·vue.js·nginx
ZC跨境爬虫2 小时前
海南大学交友平台开发实战day7(实现核心匹配算法+解决JSON请求报错问题)
前端·python·算法·html·json
来日可期13142 小时前
C/C++ 反常识记录(1)—— 那些容易踩坑的语法细节
c语言·开发语言·c++
下北沢美食家2 小时前
CSS面试题2
前端·css
实心儿儿2 小时前
C++ —— C++11(2)
开发语言·c++
weixin_461769402 小时前
npm create vue@latest 错误
前端·vue.js·npm
WindrunnerMax2 小时前
从零实现富文本编辑器#13-React非编辑节点的内容渲染
前端·架构·github