如何在 ThreeJS 中制作弯曲的矩形来显示两个列表之间的关系?
How to make curved rectangles in ThreeJS to show the relationship between two lists?
我在屏幕上有两个数据列表,一个在左边,一个在右边,我试图显示两个列表之间的关系。这些列表看起来应该有点像这张图片中间的列表。
此图片来自此网页 https://react-three-fiber-website-playground.vercel.app/,但我可以看到这是在 D3 中完成的。我需要在 Three.js 中做同样的事情,最好是 react-three-fiber。贝塞尔曲线会有帮助吗?这种数据可视化有合适的名称吗?
只是为了表明当前的线条是根据此处答案中给出的示例代码很好地绘制的。
作为选项,使用CubicBezierCurve
并根据曲线弯曲PlaneGeometry
:
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three@0.133.1";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x202020);
document.body.appendChild(renderer.domElement);
let basePoints = [ // start, end points
new THREE.Vector3(-2, -3),
new THREE.Vector3(2, 3)
];
let pathPoints = [
basePoints[0],
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[0].y),
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[1].y),
basePoints[1]
]
let curve = new THREE.CubicBezierCurve(pathPoints[0], pathPoints[1], pathPoints[2], pathPoints[3]);
let resultPoints = curve.getSpacedPoints(50);
let line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(resultPoints), new THREE.LineBasicMaterial({
color: "yellow"
}));
scene.add(line);
let width = [1, 3]; // start, end
let g = new THREE.PlaneGeometry(1, 1, 50, 1);
let pos = g.attributes.position;
for (let i = 0; i <= 50; i++) {
let lrp = THREE.MathUtils.lerp(width[0], width[1], i / 50);
pos.setXYZ(
i, resultPoints[i].x, resultPoints[i].y + lrp * 0.5 * Math.sign(pos.getY(i)), 0
);
pos.setXYZ(
i + 51, resultPoints[i].x, resultPoints[i].y + lrp * 0.5 * Math.sign(pos.getY(i + 51)), 0
)
}
let m = new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
});
let o = new THREE.Mesh(g, m);
scene.add(o);
window.addEventListener("resize", onResize);
renderer.setAnimationLoop(_ => {
renderer.render(scene, camera);
})
function onResize(event) {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
</script>
这是与 prisoner849 在 react-three-fiber 中修改为 运行 的答案相同的代码(不完全!),我现在在我的项目中使用它并且它正在运行。我认为我的 forwardRef 不是必需的,只是我在那里有 useImperativeHandle 是有原因的。
这是对问题的非回答,因为它是他刚刚翻译的代码的副本。
import React, {useLayoutEffect, useRef} from 'react'
import * as THREE from 'three'
import { useThree } from 'react-three-fiber'
const { forwardRef, useImperativeHandle } = React;
export const LineMapping = (forwardRef(({}, ref) => {
const {
camera,
gl: { domElement }
} = useThree()
const ref3 = useRef()
const ref2 = useRef()
const meshRef = useRef()
const randomFloat = (a: number, b: number): number => {
return (Math.random() * (b-a)) + a
}
let width = [randomFloat(0.3, 2.0), randomFloat(0.3, 0.6)] // start, end
let basePoints = [
// start, end points
new THREE.Vector3(-2, 0.9 + randomFloat(0.0, 3.0)),
new THREE.Vector3(2, 0.9 + randomFloat(0.0, 3.0))
]
let pathPoints = [
basePoints[0],
// @ts-ignore
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[0].y),
// @ts-ignore
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[1].y),
basePoints[1]
]
// @ts-ignore
let curve = new THREE.CubicBezierCurve(pathPoints[0], pathPoints[1], pathPoints[2], pathPoints[3])
let resultPoints = curve.getSpacedPoints(50)
const lineGeometry = new THREE.BufferGeometry().setFromPoints(resultPoints)
useLayoutEffect(() => {
if (ref3.current !== undefined && ref2.current !== undefined) {
// @ts-ignore
let pos = ref2.current!.attributes.position
for (let i = 0; i <= 50; i++) {
let iMod = i
let lrp = THREE.MathUtils.lerp(width[0], width[1], i / 50)
pos.setXYZ(i, resultPoints[iMod].x, resultPoints[iMod].y + lrp * 0.5 * Math.sign(pos.getY(i)), 0)
pos.setXYZ(iMod + 51, resultPoints[iMod].x, resultPoints[iMod].y + lrp * 0.5 * Math.sign(pos.getY(i + 51)), 0)
}
}
});
// @ts-ignore
useImperativeHandle(ref, () => ({
testMe: () => {
}
}));
return <>
<group position={[2.2, 0, -5.5]}>
<group position={[0, 0, 0]} visible={false}>
<line ref={ref3} geometry={lineGeometry}>
<lineDashedMaterial attach="material" color={'#ffffff'} linewidth={1} />
</line>
</group>
<mesh ref={meshRef} receiveShadow>
<planeBufferGeometry ref={ref2} attach="geometry" args={[7, 7, 50, 1]} />
<meshBasicMaterial attach="material" color="#2892D7" wireframe={false} />
</mesh>
</group>
</>
}))
我在屏幕上有两个数据列表,一个在左边,一个在右边,我试图显示两个列表之间的关系。这些列表看起来应该有点像这张图片中间的列表。 此图片来自此网页 https://react-three-fiber-website-playground.vercel.app/,但我可以看到这是在 D3 中完成的。我需要在 Three.js 中做同样的事情,最好是 react-three-fiber。贝塞尔曲线会有帮助吗?这种数据可视化有合适的名称吗?
只是为了表明当前的线条是根据此处答案中给出的示例代码很好地绘制的。
作为选项,使用CubicBezierCurve
并根据曲线弯曲PlaneGeometry
:
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three@0.133.1";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(scene.position);
let renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(innerWidth, innerHeight);
renderer.setClearColor(0x202020);
document.body.appendChild(renderer.domElement);
let basePoints = [ // start, end points
new THREE.Vector3(-2, -3),
new THREE.Vector3(2, 3)
];
let pathPoints = [
basePoints[0],
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[0].y),
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[1].y),
basePoints[1]
]
let curve = new THREE.CubicBezierCurve(pathPoints[0], pathPoints[1], pathPoints[2], pathPoints[3]);
let resultPoints = curve.getSpacedPoints(50);
let line = new THREE.Line(new THREE.BufferGeometry().setFromPoints(resultPoints), new THREE.LineBasicMaterial({
color: "yellow"
}));
scene.add(line);
let width = [1, 3]; // start, end
let g = new THREE.PlaneGeometry(1, 1, 50, 1);
let pos = g.attributes.position;
for (let i = 0; i <= 50; i++) {
let lrp = THREE.MathUtils.lerp(width[0], width[1], i / 50);
pos.setXYZ(
i, resultPoints[i].x, resultPoints[i].y + lrp * 0.5 * Math.sign(pos.getY(i)), 0
);
pos.setXYZ(
i + 51, resultPoints[i].x, resultPoints[i].y + lrp * 0.5 * Math.sign(pos.getY(i + 51)), 0
)
}
let m = new THREE.MeshBasicMaterial({
color: "aqua",
wireframe: true
});
let o = new THREE.Mesh(g, m);
scene.add(o);
window.addEventListener("resize", onResize);
renderer.setAnimationLoop(_ => {
renderer.render(scene, camera);
})
function onResize(event) {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
</script>
这是与 prisoner849 在 react-three-fiber 中修改为 运行 的答案相同的代码(不完全!),我现在在我的项目中使用它并且它正在运行。我认为我的 forwardRef 不是必需的,只是我在那里有 useImperativeHandle 是有原因的。 这是对问题的非回答,因为它是他刚刚翻译的代码的副本。
import React, {useLayoutEffect, useRef} from 'react'
import * as THREE from 'three'
import { useThree } from 'react-three-fiber'
const { forwardRef, useImperativeHandle } = React;
export const LineMapping = (forwardRef(({}, ref) => {
const {
camera,
gl: { domElement }
} = useThree()
const ref3 = useRef()
const ref2 = useRef()
const meshRef = useRef()
const randomFloat = (a: number, b: number): number => {
return (Math.random() * (b-a)) + a
}
let width = [randomFloat(0.3, 2.0), randomFloat(0.3, 0.6)] // start, end
let basePoints = [
// start, end points
new THREE.Vector3(-2, 0.9 + randomFloat(0.0, 3.0)),
new THREE.Vector3(2, 0.9 + randomFloat(0.0, 3.0))
]
let pathPoints = [
basePoints[0],
// @ts-ignore
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[0].y),
// @ts-ignore
new THREE.Vector2().addVectors(basePoints[0], basePoints[1]).multiplyScalar(0.5).setY(basePoints[1].y),
basePoints[1]
]
// @ts-ignore
let curve = new THREE.CubicBezierCurve(pathPoints[0], pathPoints[1], pathPoints[2], pathPoints[3])
let resultPoints = curve.getSpacedPoints(50)
const lineGeometry = new THREE.BufferGeometry().setFromPoints(resultPoints)
useLayoutEffect(() => {
if (ref3.current !== undefined && ref2.current !== undefined) {
// @ts-ignore
let pos = ref2.current!.attributes.position
for (let i = 0; i <= 50; i++) {
let iMod = i
let lrp = THREE.MathUtils.lerp(width[0], width[1], i / 50)
pos.setXYZ(i, resultPoints[iMod].x, resultPoints[iMod].y + lrp * 0.5 * Math.sign(pos.getY(i)), 0)
pos.setXYZ(iMod + 51, resultPoints[iMod].x, resultPoints[iMod].y + lrp * 0.5 * Math.sign(pos.getY(i + 51)), 0)
}
}
});
// @ts-ignore
useImperativeHandle(ref, () => ({
testMe: () => {
}
}));
return <>
<group position={[2.2, 0, -5.5]}>
<group position={[0, 0, 0]} visible={false}>
<line ref={ref3} geometry={lineGeometry}>
<lineDashedMaterial attach="material" color={'#ffffff'} linewidth={1} />
</line>
</group>
<mesh ref={meshRef} receiveShadow>
<planeBufferGeometry ref={ref2} attach="geometry" args={[7, 7, 50, 1]} />
<meshBasicMaterial attach="material" color="#2892D7" wireframe={false} />
</mesh>
</group>
</>
}))