添加材质切换,调节功能

This commit is contained in:
2025-04-14 16:37:17 +08:00
parent 0855f15a5c
commit 0929d5b80c
35 changed files with 1144 additions and 372 deletions

View File

@@ -1,251 +0,0 @@
import * as THREE from 'three';
import { Vector3 } from 'three';
import { Viewer } from './Viewer';
import { KeyBoard, MouseKey } from './KeyEnum';
//相机控制状态
export enum CameraControlState
{
Null = 0, Pan = 1, Rotate = 2, Scale = 3
}
export class CameraControls
{
m_TouthTypeList = [CameraControlState.Rotate, CameraControlState.Scale, CameraControlState.Pan];
m_domElement: HTMLElement;//HTMLDocument
//起始点击
m_StartClickPoint: THREE.Vector3 = new THREE.Vector3();
m_EndClickPoint: THREE.Vector3 = new THREE.Vector3();
m_DollyStart: THREE.Vector2 = new THREE.Vector2();
m_DollyEnd: THREE.Vector2 = new THREE.Vector2();
m_KeyDown = new Map<KeyBoard, boolean>();
m_MouseDown = new Map<MouseKey, boolean>();
//状态
m_State: CameraControlState = CameraControlState.Null;
m_Viewer: Viewer;
//左键使用旋转
m_LeftUseRotate: boolean = true;
constructor(viewer: Viewer)
{
this.m_Viewer = viewer;
this.m_domElement = viewer.Renderer.domElement.parentElement;
this.RegisterEvent();
}
RegisterEvent()
{
if (this.m_domElement)
{
this.m_domElement.addEventListener("mousedown", this.onMouseDown, false)
this.m_domElement.addEventListener("mousemove", this.onMouseMove, false)
this.m_domElement.addEventListener("mouseup", this.onMouseUp, false)
window.addEventListener("keydown", this.onKeyDown, false);
window.addEventListener("keyup", this.onKeyUp, false);
this.m_domElement.addEventListener('wheel', this.onMouseWheel, false);
this.m_domElement.addEventListener('touchstart', this.onTouchStart, false);
this.m_domElement.addEventListener('touchend', this.onTouchEnd, false);
this.m_domElement.addEventListener('touchmove', this.onTouchMove, false);
window.addEventListener("blur", this.onBlur, false);
}
}
/**
* 窗体失去焦点时.
* @memberof CameraControls
*/
onBlur = () =>
{
this.m_KeyDown.clear();
this.m_MouseDown.clear();
}
//触屏开始事件
onTouchStart = (event: TouchEvent) =>
{
this.m_Viewer.UpdateLockTarget();
this.m_StartClickPoint.set(event.touches[0].pageX, event.touches[0].pageY, 0);
if (event.touches.length < 4)
{
if (event.touches.length == 2)
{
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy);
this.m_DollyStart.set(0, distance);
}
this.m_State = this.m_TouthTypeList[event.touches.length - 1];
}
}
onTouchEnd = (event: TouchEvent) =>
{
this.m_State = CameraControlState.Null;
}
onTouchMove = (event: TouchEvent) =>
{
event.preventDefault();
event.stopPropagation();
this.m_EndClickPoint.set(event.touches[0].pageX, event.touches[0].pageY, 0);
let vec = this.m_EndClickPoint.clone().sub(this.m_StartClickPoint);
switch (this.m_State)
{
case CameraControlState.Pan:
{
this.m_Viewer.Pan(vec);
break;
}
case CameraControlState.Scale:
{
var dx = event.touches[0].pageX - event.touches[1].pageX;
var dy = event.touches[0].pageY - event.touches[1].pageY;
var distance = Math.sqrt(dx * dx + dy * dy);
this.m_DollyEnd.set(0, distance);
if (distance > this.m_DollyStart.y)
{
this.m_Viewer.Zoom(0.95);
}
else
{
this.m_Viewer.Zoom(1.05)
}
this.m_DollyStart.copy(this.m_DollyEnd);
break;
}
case CameraControlState.Rotate:
{
this.m_Viewer.Rotate(vec.multiplyScalar(2));
break;
}
}
this.m_StartClickPoint.copy(this.m_EndClickPoint);
this.m_Viewer.UpdateRender();
}
beginRotate()
{
this.m_State = CameraControlState.Rotate;
this.m_Viewer.UpdateLockTarget();
}
//最后一次按中键的时间
lastMiddleClickTime = 0;
//鼠标
onMouseDown = (event: MouseEvent) =>
{
event.preventDefault();
let key: MouseKey = event.button;
this.m_MouseDown.set(key, true);
this.m_StartClickPoint.set(event.offsetX, event.offsetY, 0);
switch (key)
{
case MouseKey.Left:
{
if (this.m_LeftUseRotate)
{
this.beginRotate();
}
break;
}
case MouseKey.Middle:
{
let curTime = Date.now();
let t = curTime - this.lastMiddleClickTime;
this.lastMiddleClickTime = curTime;
if (t < 350)
{
this.m_Viewer.ZoomAll();
return;
}
if (this.m_KeyDown.get(KeyBoard.Control))
{
this.beginRotate();
}
else
{
this.m_State = CameraControlState.Pan;
}
break;
}
case MouseKey.Right:
{
break;
}
}
}
onMouseUp = (event: MouseEvent) =>
{
event.preventDefault();
this.m_State = CameraControlState.Null;
this.m_MouseDown.set(event.button, false);
}
onMouseMove = (event: MouseEvent) =>
{
event.preventDefault();
this.m_EndClickPoint.set(event.offsetX, event.offsetY, 0);
let changeVec = this.m_EndClickPoint.clone().sub(this.m_StartClickPoint);
this.m_StartClickPoint.copy(this.m_EndClickPoint);
if (
(this.m_LeftUseRotate ||
(this.m_KeyDown.get(KeyBoard.Control))
)
&& this.m_State == CameraControlState.Rotate
)
{
this.m_Viewer.Rotate(changeVec);
}
switch (this.m_State)
{
case CameraControlState.Pan:
{
this.m_Viewer.Pan(changeVec);
break;
}
case CameraControlState.Rotate:
{
break;
}
case CameraControlState.Scale:
{
break;
}
}
}
/**
* 鼠标滚轮事件
*
* @memberof CameraControls
*/
onMouseWheel = (event: WheelEvent) =>
{
event.stopPropagation();
let pt = new THREE.Vector3(event.offsetX, event.offsetY, 0);
this.m_Viewer.ScreenToWorld(pt, new Vector3().setFromMatrixColumn(this.m_Viewer.m_Camera.Camera.matrixWorld, 2));
if (event.deltaY < 0)
{
this.m_Viewer.Zoom(0.6, pt);
}
else if (event.deltaY > 0)
{
this.m_Viewer.Zoom(1.4, pt);
}
}
//按键
onKeyDown = (event: KeyboardEvent) =>
{
this.m_KeyDown.set(event.keyCode, true);
}
onKeyUp = (event: KeyboardEvent) =>
{
this.m_KeyDown.set(event.keyCode, false);
}
}

View File

@@ -1,11 +1,10 @@
import { AmbientLight, BoxBufferGeometry, BufferGeometry, ConeBufferGeometry, CubeRefractionMapping, CubeTextureLoader, Geometry, Mesh, MeshPhysicalMaterial, Object3D, SphereBufferGeometry, sRGBEncoding, Texture, TorusBufferGeometry, TorusKnotBufferGeometry } from 'three';
import { AmbientLight, BoxBufferGeometry, BufferGeometry, Color, ConeBufferGeometry, CubeRefractionMapping, CubeTextureLoader, FrontSide, Geometry, LinearEncoding, Mesh, MeshPhysicalMaterial, Object3D, SphereBufferGeometry, sRGBEncoding, Texture, TextureLoader, TorusBufferGeometry, TorusKnotBufferGeometry, Vector3 } from 'three';
import { Singleton } from './Singleton';
import { Viewer } from './Viewer';
import { PMREMGenerator3 } from './PMREMGenerator2';
import type { PhysicalMaterialRecord, TextureTableRecord } from 'webcad_ue4_api';
import { MaterialEditorCamerControl } from './MaterialMouseControl';
import { ref } from 'vue';
import { MaterialEditorCameraControl } from './MaterialMouseControl';
async function textureRenderUpdate(textureRecord:TextureTableRecord){
const texture = textureRecord['texture'] as Texture;
@@ -29,9 +28,11 @@ async function textureRenderUpdate(textureRecord:TextureTableRecord){
*/
export class MaterialEditor extends Singleton
{
private _pointerLocked = false;
Geometrys: Map<string, Geometry | BufferGeometry>;
CurGeometryName = ref("球");
CurGeometryName = "球";
Canvas: HTMLElement;
ShowObject: Object3D;
ShowMesh: Mesh;
@@ -68,14 +69,21 @@ export class MaterialEditor extends Singleton
{
this.Viewer = new Viewer(this.Canvas);
// this.Viewer.PreViewer.Cursor.CursorObject.visible = false;
// this.Viewer.CameraCtrl.CameraType = CameraType.PerspectiveCamera;
// this.Viewer.UsePass = false;
this.initScene();
new MaterialEditorCamerControl(this.Viewer);
new MaterialEditorCameraControl(this.Viewer, this.ShowObject.position);
this.Viewer.ZoomAll();
// 初始化相机位置到观察物体的正后方
// CameraUpdate中的Rotate参数类型并非标准的欧拉角。。。
this.Viewer.RotateAround(new Vector3(0, -90 * 8, 0), this.ShowObject.position);
this.Viewer.Pan(new Vector3(0, 0, -1500));
this.Viewer.UpdateRender();
this.Viewer.Fov = 90;
}
else
{
this.Canvas.appendChild(this.Viewer.Renderer.domElement);
this.Canvas.appendChild(document);
}
}
@@ -83,28 +91,18 @@ export class MaterialEditor extends Singleton
{
this.Canvas = canvas;
this.initViewer();
this.CurGeometryName.value = "球";
this.CurGeometryName = "球";
}
initScene()
{
let scene = this.Viewer.Scene;
this.ShowObject = new Object3D();
let geo = this.Geometrys.get(this.CurGeometryName.value);
let geo = this.Geometrys.get(this.CurGeometryName);
this.ShowMesh = new Mesh(geo, this._MeshMaterial);
this.ShowMesh.scale.set(1000, 1000, 1000);
this.ShowObject.add(this.ShowMesh);
scene.add(this.ShowObject);
// let remove = autorun(() =>
// {
// let geo = this.Geometrys.get(this.CurGeometryName.get());
// if (geo)
// {
// this.ShowMesh.geometry = geo;
// this.Viewer.UpdateRender();
// }
// });
// end(this as MaterialEditor, this.dispose, remove);
//环境光
let ambient = new AmbientLight();
@@ -119,14 +117,15 @@ export class MaterialEditor extends Singleton
if (this.metaTexture) return this.metaTexture;
if (this.metaPromise) return this.metaPromise;
return new Promise((res, rej) =>
return new Promise(async (res, rej) =>
{
let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp'];
new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house/')
new CubeTextureLoader().setPath('./')
.load(urls, (t) =>
{
t.encoding = sRGBEncoding;
t.mapping = CubeRefractionMapping;
t.encoding = LinearEncoding;
let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer);
let ldrCubeRenderTarget = pmremGenerator.fromCubemap(t);
@@ -135,8 +134,6 @@ export class MaterialEditor extends Singleton
res(this.metaTexture);
this.Viewer.Scene.background = this.metaTexture;
// this.Viewer.Scene.background = new Color(255,0,0);
this.Viewer.UpdateRender();
});
});
@@ -151,12 +148,14 @@ export class MaterialEditor extends Singleton
this.exrPromise = new Promise<Texture>((res, rej) =>
{
let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp'];
new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house_gray/')
let urls = ['right-gray.webp', 'left-gray.webp', 'top-gray.webp', 'bottom-gray.webp', 'front-gray.webp', 'back-gray.webp'];
new CubeTextureLoader().setPath('./')
.load(urls, (t) =>
{
t.encoding = sRGBEncoding;
t.mapping = CubeRefractionMapping;
t.encoding = LinearEncoding;
let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer);
let target = pmremGenerator.fromCubemap(t);
this.exrTexture = target.texture;
@@ -198,21 +197,6 @@ export class MaterialEditor extends Singleton
async Update()
{
let mat = this.ShowMesh.material as MeshPhysicalMaterial;
if (this.Material.map && this.Material.map.Object && this.Material.useMap)
{
let texture = this.Material.map.Object as TextureTableRecord;
await textureRenderUpdate(texture);
}
if (this.Material.bumpMap && this.Material.bumpMap.Object)
{
let texture = this.Material.bumpMap.Object as TextureTableRecord;
await textureRenderUpdate(texture);
}
if (this.Material.roughnessMap && this.Material.roughnessMap.Object)
{
let texture = this.Material.roughnessMap.Object as TextureTableRecord;
await textureRenderUpdate(texture);
}
mat.needsUpdate = true;
this._MeshMaterial.copy(this.Material.Material);

View File

@@ -4,24 +4,25 @@ import type { Viewer } from './Viewer';
/**
* 材质编辑器的场景鼠标控制.
*/
export class MaterialEditorCamerControl
{
export class MaterialEditorCameraControl {
private Viewer: Viewer;
//State.
private _movement: Vector3 = new Vector3();
private _MouseIsDown: boolean = false;
private _StartPoint: Vector3 = new Vector3();
private _EndPoint = new Vector3();
private pointId: number;
constructor(view: Viewer)
{
this.Viewer = view;
private _target: Vector3 | null = null;
get Target() { return this._target; }
set Target(val: Vector3 | null) { this._target = val; }
constructor(view: Viewer, target: Vector3 | null = null) {
this.Viewer = view;
this.Target = target;
this.initMouseControl();
}
initMouseControl()
{
initMouseControl() {
let el = this.Viewer.Renderer.domElement;
el.addEventListener("pointerdown", (e) => { this.pointId = e.pointerId; }, false);
el.addEventListener("mousedown", this.onMouseDown, false);
@@ -29,28 +30,23 @@ export class MaterialEditorCamerControl
el.addEventListener("mouseup", this.onMouseUp, false);
el.addEventListener('wheel', this.onMouseWheel, false);
}
onMouseDown = (event: MouseEvent) =>
{
onMouseDown = (event: MouseEvent) => {
this.requestPointerLock();
this._MouseIsDown = true;
this._StartPoint.set(event.offsetX, event.offsetY, 0);
};
onMouseUp = (event: MouseEvent) =>
{
onMouseUp = (event: MouseEvent) => {
this._MouseIsDown = false;
this.exitPointerLock();
};
onMouseMove = (event: MouseEvent) =>
{
onMouseMove = (event: MouseEvent) => {
event.preventDefault();
if (this._MouseIsDown)
{
this._EndPoint.set(event.offsetX, event.offsetY, 0);
let changeVec: Vector3 = new Vector3();
changeVec.subVectors(this._EndPoint, this._StartPoint);
this._StartPoint.copy(this._EndPoint);
if (this._MouseIsDown) {
this.Viewer.Rotate(changeVec);
this._movement.set(event.movementX, event.movementY, 0);
if (this.Target) {
this.Viewer.RotateAround(this._movement, this.Target);
}
else this.Viewer.Rotate(this._movement);
}
};
onMouseWheel = (event: WheelEvent) =>
@@ -65,21 +61,25 @@ export class MaterialEditorCamerControl
}
};
requestPointerLock()
{
if (this.Viewer.Renderer.domElement.setPointerCapture)
this.Viewer.Renderer.domElement.setPointerCapture(this.pointId);
requestPointerLock() {
const dom = this.Viewer.Renderer.domElement;
if (dom.setPointerCapture)
dom.setPointerCapture(this.pointId);
if (dom.requestPointerLock)
dom.requestPointerLock();
}
exitPointerLock()
{
if (this.Viewer.Renderer.domElement.releasePointerCapture && this.pointId !== undefined)
{
try
{
this.Viewer.Renderer.domElement.releasePointerCapture(this.pointId);
exitPointerLock() {
const dom = this.Viewer.Renderer.domElement;
if (dom.releasePointerCapture && this.pointId !== undefined) {
try {
dom.releasePointerCapture(this.pointId);
}
catch (error) { }
}
if (document.exitPointerLock)
document.exitPointerLock();
}
}

View File

@@ -0,0 +1,94 @@
import { AmbientLight, BoxBufferGeometry, Camera, Material, Mesh, MeshPhysicalMaterial, OrthographicCamera, PointLight, Scene, Sprite, SpriteMaterial, WebGLRenderer } from 'three';
export class MaterialRenderer
{
sprite: Sprite;
sphere: Mesh;
scene: Scene;
camera: Camera;
canvas: HTMLCanvasElement;
renderer: WebGLRenderer;
constructor()
{
//Renderer
this.renderer = new WebGLRenderer({ alpha: true, antialias: true });
this.renderer.setSize(300, 300);
//Canvas
this.canvas = this.renderer.domElement;
//Camera
this.camera = new OrthographicCamera(-1, 1, 1, -1, -1, 1);
//Scene
this.scene = new Scene();
//Sphere
this.sphere = new Mesh(new BoxBufferGeometry(2, 2, 2));
this.scene.add(this.sphere);
//Sprite
this.sprite = new Sprite();
this.sprite.scale.set(2, 2, 1);
this.scene.add(this.sprite);
//Ambient light
var ambient = new AmbientLight();
this.scene.add(ambient);
//Pontual light
var point = new PointLight();
point.position.set(-0.5, 1, 1.5);
this.scene.add(point);
}
//Set render size
setSize(x: number, y: number)
{
this.renderer.setSize(x, y);
}
update(material: Material)
{
//Set material
if (material instanceof SpriteMaterial)
{
this.sphere.visible = false;
this.sprite.visible = true;
this.sprite.material = material;
this.camera.position.set(0, 0, 0.5);
}
else
{
this.sprite.visible = false;
this.sphere.visible = true;
this.sphere.material = material as MeshPhysicalMaterial;
this.camera.position.set(0, 0, 1.5);
}
this.camera.updateMatrix();
//Render
this.renderer.render(this.scene, this.camera);
}
getBlob(material: Material): Promise<Blob>
{
this.update(material);
return new Promise(res =>
{
this.canvas.toBlob(b => res(b));
});
}
render(material: Material)
{
this.update(material);
return this.canvas.toDataURL();
};
}
export const materialRenderer = new MaterialRenderer();

View File

@@ -0,0 +1,22 @@
import { CADFiler, Database, DuplicateRecordCloning, PhysicalMaterialRecord } from "webcad_ue4_api";
export function MaterialOut(material: PhysicalMaterialRecord): string
{
let db = new Database();
debugger;
db.WblockCloneObejcts(
[material],
db.MaterialTable,
new Map(),
DuplicateRecordCloning.Ignore
);
return JSON.stringify(db.FileWrite().Data);
}
export function MaterialIn(fileData: Object[]): PhysicalMaterialRecord
{
let f = new CADFiler(fileData);
let db = new Database().FileRead(f);
db.hm.Enable = false;
return db.MaterialTable.Symbols.entries().next().value[1];
}

View File

@@ -1,13 +1,11 @@
import { Raycaster, Scene, Vector3, WebGLRenderer, type WebGLRendererParameters } from "three";
import { Raycaster, Scene, Vector3, WebGLRenderer, type Vector, type WebGLRendererParameters } from "three";
import { cZeroVec, GetBox, GetBoxArr } from "./GeUtils";
import { PlaneExt } from "./PlaneExt";
import { CameraUpdate } from "webcad_ue4_api";
import { CameraControls } from "./CameraControls";
import { CameraType, CameraUpdate } from "webcad_ue4_api";
export class Viewer {
m_LookTarget: any;
m_Camera: CameraUpdate = new CameraUpdate();
CameraCtrl: CameraControls;
protected NeedUpdate: boolean = true;
Renderer: WebGLRenderer;//渲染器 //暂时只用这个类型
m_DomEl: HTMLElement; //画布容器
@@ -21,6 +19,15 @@ export class Viewer {
return this._Scene;
}
get Fov() {
return this.m_Camera.Fov;
}
set Fov(val: number) {
this.m_Camera.Fov = val;
this.NeedUpdate = true;
}
UpdateRender()
{
this.NeedUpdate = true;
@@ -37,10 +44,10 @@ export class Viewer {
this.initRender(canvasContainer);
this.OnSize();
this.StartRender();
this.CameraCtrl = new CameraControls(this);
window.addEventListener("resize", () => {
this.OnSize();
});
this.InitCamera();
}
//初始化render
@@ -73,6 +80,11 @@ export class Viewer {
this.OnSize();
}
InitCamera() {
this.m_Camera.CameraType = CameraType.PerspectiveCamera;
this.m_Camera.Near = 0.001;
this.m_Camera.Camera.far = 1000000000;
}
OnSize = (width?: number, height?: number) => {
this._Width = width ? width : this.m_DomEl.clientWidth;
this._Height = height ? height : this.m_DomEl.clientHeight;
@@ -91,6 +103,7 @@ export class Viewer {
requestAnimationFrame(this.StartRender);
if (this._Scene != null && this.NeedUpdate) {
this.Render();
this.m_Camera.Update();
this.NeedUpdate = false;
}
};
@@ -139,6 +152,10 @@ export class Viewer {
this.m_Camera.Rotate(mouseMove, this.m_LookTarget);
this.NeedUpdate = true;
}
RotateAround(movement: Vector3, center: Vector3) {
this.m_Camera.Rotate(movement, center);
this.NeedUpdate = true;
}
Pan(mouseMove: Vector3) {
this.m_Camera.Pan(mouseMove);
this.NeedUpdate = true;