Custom edit 3D room tools (a) draw lines into walls

CTRL + C, CTRL + V; Recently I had a free time and picked this up again…

Basic effect:

First, principle review

Canvas drawing ——> Generate data ——> Render 3D

Second, added a new module

Glass walls, doors, custom flooring

Canvas part

The floor and wall inside are realized with simple lines (CTx.lineto) and squares (CTX.fillrect). For details, please refer to issue 1.

Here in particular is: painting door

Because the door must be on the wall, and the Angle of rotation of the door must be the same as that of the wall, so when you draw a door, you can’t draw it like a wall.

Its interaction should be: click a point on the wall to draw the door along the wall, click the position as the center of the door. (Click position should be corrected according to the center line of the wall, so that the door drawn is the center of the wall.)

1. Use isPointInStroke method to determine which wall to click on and use the parameters of the wall

ctx.isPointInStroke(activePos[0], activePos[1])
Copy the code

2. Correct the coordinates of the door

// Get the gate coordinates. Const getDoorPos = (wallData: [[number, number], [number, number]], pos: [number, number]) => { let result = pos; const start = wallData[0]; const end = wallData[1]; If (end [0] - start [0] && end [1] - start [1]) {/ / click point not on the wall of the center line / / wall function const k = (end [1] - start [1])/(end [0] - start [0]). const b = end[1] - k * end[0]; Const b2 = pos[1] + k * pos[0]; // const y = (b + b2) / 2; const x = (b2 - b) / (2 * k); result = [x, y]; } return result; };Copy the code

3, use a common line drawing method, painting doors

// Draw gate if (activeNow? .type === 'wall') { const params: any = { id: new Date().getTime(), type: active, door: getDoorPos(activeNow.data, endPo), wallId: activeNow.id, data: activeNow.data, }; lines.push(params); } const drawLine = ( ctx: any, datas: [[number, number], [number, number]], config? : { type? : ActiveDrawType; width? : number; }, ) => { if (! datas || ! ctx || ! datas.length) { return; } const { type = 'wall', width = 8 } = config || {}; const wallColorNow = wallColor[type]; const start = datas[0]; const end = datas[1]; ctx.beginPath(); // start path ctx.lineWidth = width; ctx.strokeStyle = wallColorNow; ctx.setLineDash([]); ctx.moveTo(start[0], start[1]); Ctx.lineto (end[0], end[1]); ctx.lineto (end[0], end[1]); Ctx.closepath (); ctx.stroke(); };Copy the code

Part three

Note: BSP and threejs versions

“@types/three”: “^0.126.1”,

BSP: I used it from the Internet guru himself. (Informal version) I took some key BSP tweaking code and put it at the end of the article.

Umi and Threejs/Fiber

Umi is used in my business architecture because UMI will unify the processing of prop.children. I can’t use threejs/ Fiber!! (This is part of my joke)

BSP hole digging code, base wall code

Function createResultBsp(BSP: any, less_bsp: any, mat: any) {let material = wallGrayMaterial; switch (mat) { case 1: material = wallPurpleMaterial; break; case 2: material = wallGrayMaterial; break; } const sphere1BSP = new ThreeBSP(bsp); const cube2BSP = new ThreeBSP(less_bsp); const resultBSP = sphere1BSP.subtract(cube2BSP); const result = resultBSP.toMesh(material); result.material.flatshading = THREE.FlatShading; // result.geometry.computeFaceNormals(); / / to recalculate the geometry profile normal vector result.geometry.com puteVertexNormals (); result.material.needsUpdate = true; / / update the texture result. Geometry. BuffersNeedUpdate = true; result.geometry.uvsNeedUpdate = true; return result; Function returnWallObject(width: any, height: any, depth: any, Angle: any, material: any, x: any, y: any, z: any, ) { const cubeGeometry = new THREE.BoxGeometry(width, height, depth); const cube = new THREE.Mesh(cubeGeometry, material); cube.position.x = x; cube.position.y = y; cube.position.z = z; cube.rotation.y = angle; return cube; } function createGlassWall(datas: any[]) { const { height = 0, glassHeight = 0, glassDepth = 1 } = configWall || {}; const left_wall = createCubeWall(datas, { material: matArrayB, }); const left_cube = createCubeWall(datas, { material: matArrayB, height: glassHeight, y: (height - glassHeight) / 2, }); const wallBsp = createResultBsp(left_wall, left_cube, 1); const cube = createCubeWall(datas, { material: glass_material, height: glassHeight, y: (height - glassHeight) / 2, depth: glassDepth, }); const bspGroup = new THREE.Group(); bspGroup.add(wallBsp); bspGroup.add(cube); return bspGroup; }Copy the code

3. Select and delete modules

Core idea: Click to judge the selected module, draw the middle line, click the button, batch delete

1. Judgment of module selection

Ctx.ispointinstroke (activePos[0], activePos[1])

ContainStroke (item.data, activePos[0], activePos[1])

const drawBoxs = ({ ctx, lines, activeLines, activePos, }: // endPo, // active, { ctx: any; lines: Record<string, any>[]; activeLines? : string[]; activePos? : any[]; endPo? : any[]; active? : string; }) => { let activeItem: any; lines.forEach((item: any) => { let func: any = drawLine; const params: any = { type: item.type }; if (item.type === 'floor') { func = drawFloor; } else if (item.type === 'door') { func = drawDoor; params.door = item.door; } func(ctx, item.data, params); ActivePos && activePos. Length) {if (item.type === 'floor') {if (containStroke(item.data, activePos[0], activePos[1])) { activeItem = item; } } else if (ctx.isPointInStroke(activePos[0], activePos[1])) { activeItem = item; }} // The box in the selection if (activeLines? .includes(item.id)) { let start = item.data[0]; let end = item.data[1]; if (item.type === 'door') { [start, end] = getDoorData(start, end, item.door); } drawBorder({ ctx, isRect: item.type === 'floor', start, end, }); }}); return activeItem; };Copy the code

2. Draw the dotted line

Principle:

We know the coordinates of start and end. We know the wall width.

Calculate the coordinates of points A, B, C and D.

Ok, use our trigonometric functions blah blah blah. (Pity me, an old social dog, for forgetting the trig function)

Const drawBorder = (props: {CTX: any; lineWidth? : number; strokeStyle? : string; lineDash? : [number, number]; start: [number, number]; end: [number, number]; width? : number; isRect? : boolean; isDoor? : boolean; }) => { const { ctx, lineWidth = 2, strokeStyle = '#f5222d', lineDash = [6, 6], width = 8, start, end, isRect, } = props || {}; ctx.beginPath(); // Start path ctx.lineWidth = lineWidth; ctx.strokeStyle = strokeStyle; ctx.setLineDash(lineDash); If (isRect) {ctx.strokerect (start[0], start[1], end[0] -start [0], end[1] -start [1]); } else { let point1x = 0, point1y = 0; const width2 = width / 2; -k const k = (end[1] -start [1])/(end[0] -start [0]); if (end[0] -start [0]) {const k = (end[1] -start [1])/(end[0] -start [0]); const startb = start[1] + k * start[0]; Const spoint0Y = start[1] -startb; const spoint0Y = start[1] -startb; Const spoint0StartXY = math.pow (math.pow (spoint0Y, 2) + math.pow (start[0], 2), 0.5); point1y = width2 * (start[0] / spoint0StartXY); point1x = width2 * (spoint0Y / spoint0StartXY); } else {// while standing point1x = width2; } ctx.moveTo(start[0] + point1x, start[1] + point1y); Ctx. lineTo(start[0] -point1x, start[1] -point1y); ctx.lineTo(end[0] - point1x, end[1] - point1y); ctx.lineTo(end[0] + point1x, end[1] + point1y); ctx.lineTo(start[0] + point1x, start[1] + point1y); ctx.closePath(); } ctx.stroke(); };Copy the code

3, delete,

Delete it. There’s nothing to say. We started by separating rendering and data.

I’m just going to delete the corresponding data here.

Fourth, the end

Happy New Year to all of you

BSP key adjustment code

ThreeBSP.prototype.toTree = function (treeIsh) { if (treeIsh instanceof ThreeBSP.Node) { return treeIsh; } // Look at the source code of three. js 0.126, all types of Geometry are basically inherited or implemented by BufferGeometry, basically Three. Therefore, it is necessary to obtain point, normal vector, and UV information from the former faces of THREE.Geometry to obtain var polygons = [] from the attributes of THREE. geometry = treeIsh === THREE.BufferGeometry || (treeIsh.type || '').endsWith('Geometry') ? treeIsh : treeIsh.constructor === THREE.Mesh ? (treeIsh.updateMatrix(), (this.matrix = treeIsh.matrix.clone()), treeIsh.geometry) : void 0; {// TODO does not validate position, normal, and UV for the time being. Var attributes = geometry. Attributes, normal = attributes. Normal, position = attributes.position, uv = attributes.uv; / / some var pointsLength = the number of attributes. The position. The array. The length/attributes. The position. The itemSize; Var pointsArr = [], normalsArr = [], uvsArr = []; var pointsArr = [], normalsArr = []; For (var I = 0, len = pointsLength; i < len; Var startIndex = 3 * I; var startIndex = 3 * I; pointsArr.push( new THREE.Vector3( position.array[startIndex], position.array[startIndex + 1], position.array[startIndex + 2], ), ); normalsArr.push( new THREE.Vector3( normal.array[startIndex], normal.array[startIndex + 1], normal.array[startIndex + 2], ), ); uvsArr.push(new THREE.Vector2(uv.array[2 * i], uv.array[2 * i + 1])); } var index = geometry.index.array; for (var i = 0, len = index.length; i < len; ) { var polygon = new ThreeBSP.Polygon(); Var j = 0; for (var j = 0; j < 3; j++) { var pointIndex = index[i], point = pointsArr[pointIndex]; var vertex = new ThreeBSP.Vertex( point.x, point.y, point.z, normalsArr[pointIndex], uvsArr[pointIndex], ); vertex.applyMatrix4(this.matrix); polygon.vertices.push(vertex); i++; } polygons.push(polygon.calculateProperties()); }} else {for (var I = 0, len = pointsLength; i < len; ) { var polygon = new ThreeBSP.Polygon(); Var j = 0; for (var j = 0; j < 3; j++) { var startIndex = 3 * i; var vertex = new ThreeBSP.Vertex( position.array[startIndex], position.array[startIndex + 1], position.array[startIndex + 2], new THREE.Vector3( normal.array[startIndex], normal.array[startIndex + 1], normal.array[startIndex + 2], ), new THREE.Vector2(uv.array[2 * i], uv.array[2 * i + 1]), ); vertex.applyMatrix4(this.matrix); polygon.vertices.push(vertex); i++; } polygons.push(polygon.calculateProperties()); }}} else {console.error(' Please check initialization parameters to get geometry data when initializing ThreeBSP '); } return new ThreeBSP.Node(polygons); };Copy the code