From 34c9b3015fced30305529f650cf3fb5e89ffad1c Mon Sep 17 00:00:00 2001 From: york Date: Wed, 24 Jul 2024 08:26:02 +0800 Subject: [PATCH] feat: plane sphere intersection --- dev/webgl-3d-put.story.tsx | 61 +++++++++++++++++++++++++++++++++++++- src/utils/plane.ts | 8 +++++ src/utils/sphere.ts | 22 ++++++++++++-- 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/dev/webgl-3d-put.story.tsx b/dev/webgl-3d-put.story.tsx index c0b39445..63744438 100644 --- a/dev/webgl-3d-put.story.tsx +++ b/dev/webgl-3d-put.story.tsx @@ -1,7 +1,7 @@ import * as React from "react" import { produce } from 'immer' import * as twgl from 'twgl.js' -import { createWebgl3DRenderer, Graphic3d, useWindowSize, angleToRadian, Vec3, useGlobalKeyDown, Nullable, useUndoRedo, metaKeyIfMacElseCtrlKey, Menu, colorNumberToVec, NumberEditor, arcToPolyline, circleToArc, MenuItem, vecToColorNumber, SphereGeometry, CubeGeometry, Vec4, WeakmapCache, useLocalStorageState, Position3D, getLineAndSphereIntersectionPoints, position3DToVec3, slice3, vec3ToPosition3D, Button, GeneralFormPlane, CylinderGeometry, getLineAndPlaneIntersectionPoint, getThreePointPlane, getLineAndCylinderIntersectionPoints, getTwoLine3DIntersectionPoint, v3, getVerticesTriangles, getLineAndTrianglesIntersectionPoint, ConeGeometry, getLineAndConeIntersectionPoints } from "../src" +import { createWebgl3DRenderer, Graphic3d, useWindowSize, angleToRadian, Vec3, useGlobalKeyDown, Nullable, useUndoRedo, metaKeyIfMacElseCtrlKey, Menu, colorNumberToVec, NumberEditor, arcToPolyline, circleToArc, MenuItem, vecToColorNumber, SphereGeometry, CubeGeometry, Vec4, WeakmapCache, useLocalStorageState, Position3D, getLineAndSphereIntersectionPoints, position3DToVec3, slice3, vec3ToPosition3D, Button, GeneralFormPlane, CylinderGeometry, getLineAndPlaneIntersectionPoint, getThreePointPlane, getLineAndCylinderIntersectionPoints, getTwoLine3DIntersectionPoint, v3, getVerticesTriangles, getLineAndTrianglesIntersectionPoint, ConeGeometry, getLineAndConeIntersectionPoints, getPlaneSphereIntersection } from "../src" export default () => { const ref = React.useRef(null) @@ -115,6 +115,15 @@ export default () => { color: s.color, position: [0, 0, 0], } + } else if (s.geometry.type === 'line strip') { + return { + geometry: { + type: 'line strip', + points: s.geometry.points.flat(), + }, + color: s.color, + position: [0, 0, 0], + } } return { geometry: s.geometry, @@ -160,6 +169,15 @@ export default () => { color: [0, 1, 0, 1], position: [0, 0, 0.5], }) + } else if (g.geometry.type === 'line strip') { + graphics.push({ + geometry: { + type: 'line strip', + points: g.geometry.points.map(p => [p[0], p[1], p[2] + 0.5]).flat(), + }, + color: [0, 1, 0, 1], + position: [0, 0, 0], + }) } if (radius) { const points = arcToPolyline(circleToArc({ x: 0, y: 0, r: radius }), 5) @@ -349,6 +367,7 @@ export default () => { const target1 = getGraphic(state1) const target2 = getGraphic(state2) let points: Vec3[] | undefined + let lines: Vec3[] | undefined if (target1.geometry.type === 'lines') { if (target2.geometry.type === 'lines') { const p1 = slice3(target1.geometry.points) @@ -415,6 +434,18 @@ export default () => { ...(vec3ToPosition3D(target1.position || [0, 0, 0])), } ) + } else if (state2.geometry.type === 'triangle') { + const plane = getThreePointPlane( + position3DToVec3(state2.position), + position3DToVec3(state2.geometry.p1), + position3DToVec3(state2.geometry.p2), + ) + if (plane) { + lines = getPlaneSphereIntersection(plane, { + radius: target1.geometry.radius, + ...(vec3ToPosition3D(target1.position || [0, 0, 0])), + }) + } } } else if (target1.geometry.type === 'cylinder') { if (target2.geometry.type === 'lines') { @@ -455,6 +486,18 @@ export default () => { points = [p] } } + } else if (target2.geometry.type === 'sphere') { + const plane = getThreePointPlane( + position3DToVec3(state1.position), + position3DToVec3(state1.geometry.p1), + position3DToVec3(state1.geometry.p2), + ) + if (plane) { + lines = getPlaneSphereIntersection(plane, { + radius: target2.geometry.radius, + ...(vec3ToPosition3D(target2.position || [0, 0, 0])), + }) + } } } else if (target1.geometry.type === 'cube') { if (target2.geometry.type === 'lines') { @@ -475,6 +518,19 @@ export default () => { }) setStatus(undefined) } + if (lines && lines.length > 0) { + setState(draft => { + draft.push({ + geometry: { + type: 'line strip', + points: lines, + }, + color: [0, 1, 0, 1], + position: { x: 0, y: 0, z: 0 }, + }) + }) + setStatus(undefined) + } } } }} @@ -820,6 +876,9 @@ interface State { type: 'triangle' p1: Position3D p2: Position3D + } | { + type: 'line strip' + points: Vec3[] } color: Vec4 position: Position3D diff --git a/src/utils/plane.ts b/src/utils/plane.ts index 1012fbd4..45c9ae9e 100644 --- a/src/utils/plane.ts +++ b/src/utils/plane.ts @@ -159,3 +159,11 @@ export function rotateDirectionByRadianOnPlane(direction: Vec3, radian: number, if (!p1) return return v3.normalize(p1) } + +export function getCoordinate(direction: Vec3): Tuple3 { + direction = v3.normalize(direction) + const d1: Vec3 = isZero(direction[0]) && isZero(direction[1]) ? [1, 0, 0] : [0, 0, 1] + const d2 = v3.normalize(v3.cross(direction, d1)) + const d3 = v3.cross(direction, d2) + return [d2, d3, direction] +} diff --git a/src/utils/sphere.ts b/src/utils/sphere.ts index e81f8c17..278c4e90 100644 --- a/src/utils/sphere.ts +++ b/src/utils/sphere.ts @@ -1,8 +1,11 @@ +import { arcToPolyline, circleToArc } from "./circle" import { calculateEquation2 } from "./equation-calculater" import { equals, isZero } from "./math" -import { v3 } from "./matrix" +import { matrix, v3 } from "./matrix" +import { GeneralFormPlane, getCoordinate, getPerpendicularPointToPlane } from "./plane" import { Position3D, getPointByLengthAndDirection3D, getTwoPointsDistance3D, position3DToVec3, vec3ToPosition3D } from "./position" -import { Vec3 } from "./types" +import { getCoordinateMatrix, getCoordinateVec } from "./transform" +import { slice3, Tuple4, Vec3 } from "./types" import { and, number } from "./validators" export interface Sphere extends Position3D { @@ -69,3 +72,18 @@ export function getParallelSpheresByDistance(sphere: Sphere, distance: number): export function getSphereNormalAtPoint(sphere: Sphere, point: Vec3): Vec3 { return v3.substract(point, position3DToVec3(sphere)) } + +export function getPlaneSphereIntersection(plane: GeneralFormPlane, sphere: Sphere) { + const center = position3DToVec3(sphere) + const p = getPerpendicularPointToPlane(center, plane) + const direction = v3.substract(p, center) + const r = Math.sqrt(sphere.radius ** 2 - v3.lengthSquare(direction)) + if (isNaN(r)) return + // x = r cos(t) + // y = r sin(t) + // z = 0 + const coordinate: Tuple4 = [p, ...getCoordinate(direction)] + const m = getCoordinateMatrix(coordinate) + if (!m) return + return arcToPolyline(circleToArc({ x: 0, y: 0, r }), 5).map(n => slice3(matrix.multiplyVec(m, getCoordinateVec([n.x, n.y, 0], coordinate)))) +}