续上章继续实现智能黑板示例:
使用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. 基础绘制
- 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);
}
}
}
- 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 物体定义
- 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;
}
}
- 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 ;
}
}
- 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 逻辑控制
- 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;
}
}
}
}
- 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);
}
}
- 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;
}
}
}
未完待续~~~~~~~~~~~~~~