feat: 主页文案样式修改,细胞结构项目增加交互点.
|
|
@ -24,7 +24,7 @@
|
|||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>3D</title>
|
||||
<title>dragonir's work list</title>
|
||||
</head>
|
||||
<body style="background: #212121;">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
import * as THREE from "three";
|
||||
|
||||
// 创建文字
|
||||
export const makeTextSprite = (text, color, parameters) => {
|
||||
if (parameters === undefined) parameters = {};
|
||||
var fontface = parameters.hasOwnProperty("fontface") ? parameters["fontface"] : "Arial";
|
||||
var fontsize = parameters.hasOwnProperty("fontsize") ? parameters["fontsize"] : 32;
|
||||
var borderThickness = parameters.hasOwnProperty("borderThickness") ? parameters["borderThickness"] : 4;
|
||||
var borderColor = parameters.hasOwnProperty("borderColor") ? parameters["borderColor"] : { r: 0, g: 0, b: 0, a: 1.0 };
|
||||
var canvas = document.createElement('canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
context.font = fontsize + "px " + fontface;
|
||||
var metrics = context.measureText(text);
|
||||
var textWidth = metrics.width;
|
||||
context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + "," + borderColor.b + "," + borderColor.a + ")";
|
||||
context.lineWidth = borderThickness;
|
||||
context.fillStyle = color ? color : "#ffffff";
|
||||
context.fillText(text, borderThickness, fontsize + borderThickness);
|
||||
context.font = 48 + "px " + fontface;
|
||||
context.width = textWidth;
|
||||
var texture = new THREE.Texture(canvas);
|
||||
texture.needsUpdate = true;
|
||||
var spriteMaterial = new THREE.SpriteMaterial({ map: texture });
|
||||
var sprite = new THREE.Sprite(spriteMaterial);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
// 创建圆形文字
|
||||
export const makeCycleTextSprite = (text, W = 100, H = 100, borderWidth = 6, borderColor = 'white', color = 'black', textColor = 'white') => {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
var ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
ctx.arc((W + borderWidth) / 2, (H + borderWidth) / 2, 40, 0, Math.PI * 2, true);
|
||||
ctx.closePath();
|
||||
// 填充背景颜色
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
// 填充边框颜色
|
||||
ctx.lineWidth = borderWidth;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.strokeStyle = borderColor;
|
||||
ctx.stroke();
|
||||
// 填充文字颜色
|
||||
ctx.font = "64px Arial";
|
||||
ctx.fillStyle = textColor;
|
||||
ctx.textAlign = "center";
|
||||
var metrics = ctx.measureText(text);
|
||||
ctx.fillText(text, (W + borderWidth) / 2, (H + borderWidth * 2) / 2 + metrics.fontBoundingBoxDescent + metrics.actualBoundingBoxDescent * 2);
|
||||
var texture = new THREE.Texture(canvas);
|
||||
texture.needsUpdate = true;
|
||||
var spriteMaterial = new THREE.SpriteMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: .8,
|
||||
});
|
||||
var sprite = new THREE.Sprite(spriteMaterial);
|
||||
return sprite;
|
||||
}
|
||||
|
|
@ -5,12 +5,28 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
|||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||
import plantCellModel from './models/plant_cell.glb';
|
||||
import AnimalCellModel from './models/animal_cell.glb';
|
||||
import Stats from "three/examples/jsm/libs/stats.module";
|
||||
import { makeCycleTextSprite } from '../../assets/utils/common';
|
||||
import './index.css';
|
||||
|
||||
export default class Cell extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.plantCell = null;
|
||||
this.plantGroup = new THREE.Group;
|
||||
this.plantPoint = [
|
||||
{ key: '1', value: '细胞壁', location: { x: 0, y: 4.2, z: -1 }},
|
||||
{ key: '2', value: '细胞壁', location: { x: -7, y: 4, z: -1.6 }},
|
||||
{ key: '3', value: '细胞壁', location: { x: 5, y: 10, z: -10 }},
|
||||
{ key: '4', value: '细胞壁', location: { x: 0, y: 16, z: 10 }},
|
||||
{ key: '5', value: '细胞壁', location: { x: 0, y: 10, z: 0 }},
|
||||
{ key: '6', value: '细胞壁', location: { x: 3, y: 10, z: 2 }},
|
||||
{ key: '7', value: '细胞壁', location: { x: 0, y: 15, z: 0 }},
|
||||
{ key: '8', value: '细胞壁', location: { x: 8, y: 8, z: 8 }},
|
||||
]
|
||||
this.animalCell = null;
|
||||
this.animalGroup = new THREE.Group;
|
||||
}
|
||||
|
||||
state = {
|
||||
|
|
@ -25,7 +41,7 @@ export default class Cell extends React.Component {
|
|||
|
||||
initThree = () => {
|
||||
var container, controls, stats;
|
||||
var camera, scene, renderer, light, earthMeshes = [];
|
||||
var camera, scene, renderer, light, interactableMeshes = [];
|
||||
var _this = this;
|
||||
init();
|
||||
animate();
|
||||
|
|
@ -44,6 +60,10 @@ export default class Cell extends React.Component {
|
|||
camera.position.set(0, 16, 18);
|
||||
camera.lookAt(new THREE.Vector3(0, 0, 0));
|
||||
|
||||
// threejs中采用的是右手坐标系,红线是X轴,绿线是Y轴,蓝线是Z轴
|
||||
var axes = new THREE.AxisHelper(30);
|
||||
scene.add(axes);
|
||||
|
||||
const cubeGeometry = new THREE.BoxGeometry(0.001, 0.001, 0.001);
|
||||
const cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xdc161a });
|
||||
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
|
||||
|
|
@ -51,14 +71,7 @@ export default class Cell extends React.Component {
|
|||
light = new THREE.DirectionalLight(0x03c03c, 1);
|
||||
light.intensity = 1.2;
|
||||
light.position.set(-5, 10, 3);
|
||||
light.castShadow = true;
|
||||
light.target = cube;
|
||||
light.shadow.mapSize.width = 512 * 12;
|
||||
light.shadow.mapSize.height = 512 * 12;
|
||||
light.shadow.camera.top = 10;
|
||||
light.shadow.camera.bottom = - 5;
|
||||
light.shadow.camera.left = - 5;
|
||||
light.shadow.camera.right = 10;
|
||||
scene.add(light);
|
||||
|
||||
var ambientLight = new THREE.AmbientLight(0xffffff);
|
||||
|
|
@ -85,9 +98,6 @@ export default class Cell extends React.Component {
|
|||
loader.load(plantCellModel, function (mesh) {
|
||||
mesh.scene.traverse(function (child) {
|
||||
if (child.isMesh) {
|
||||
earthMeshes.push(child)
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
child.material.metalness = 1;
|
||||
child.material.roughness = 0;
|
||||
}
|
||||
|
|
@ -95,15 +105,23 @@ export default class Cell extends React.Component {
|
|||
mesh.scene.position.set(0, 0, 0);
|
||||
mesh.scene.scale.set(40, 40, 40);
|
||||
_this.plantCell = mesh.scene;
|
||||
scene.add(mesh.scene);
|
||||
_this.plantGroup.add(mesh.scene);
|
||||
|
||||
_this.plantPoint.map(item => {
|
||||
let point = makeCycleTextSprite(item.key);
|
||||
point.name = item.value;
|
||||
point.scale.set(1, 1, 1);
|
||||
point.position.set(item.location.x, item.location.y, item.location.z);
|
||||
_this.plantGroup.add(point);
|
||||
interactableMeshes.push(point);
|
||||
});
|
||||
|
||||
scene.add(_this.plantGroup);
|
||||
});
|
||||
// 动物细胞
|
||||
loader.load(AnimalCellModel, function (mesh) {
|
||||
mesh.scene.traverse(function (child) {
|
||||
if (child.isMesh) {
|
||||
earthMeshes.push(child)
|
||||
child.castShadow = true;
|
||||
child.receiveShadow = true;
|
||||
child.material.metalness = 1;
|
||||
child.material.roughness = 0;
|
||||
}
|
||||
|
|
@ -111,6 +129,7 @@ export default class Cell extends React.Component {
|
|||
mesh.scene.position.set(0, -8, 0);
|
||||
mesh.scene.scale.set(120, 120, 120);
|
||||
_this.animalCell = mesh.scene;
|
||||
_this.animalGroup.add(mesh.scene);
|
||||
});
|
||||
|
||||
controls = new OrbitControls(camera, renderer.domElement);
|
||||
|
|
@ -131,11 +150,39 @@ export default class Cell extends React.Component {
|
|||
stats && stats.update();
|
||||
controls && controls.update();
|
||||
}
|
||||
|
||||
stats = new Stats();
|
||||
document.documentElement.appendChild(stats.dom);
|
||||
|
||||
// 增加点击事件,声明raycaster和mouse变量
|
||||
var raycaster = new THREE.Raycaster();
|
||||
var mouse = new THREE.Vector3();
|
||||
function onMouseClick(event) {
|
||||
// 通过鼠标点击的位置计算出raycaster所需要的点的位置,以屏幕中心为原点,值的范围为-1到1.
|
||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
|
||||
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
|
||||
// 屏幕坐标转标准设备坐标
|
||||
const x1 = ( event.clientX / window.innerWidth ) * 2 - 1;
|
||||
const y1 = -( event.clientX / window.innerHeight ) * 2 + 1;
|
||||
//标准设备坐标(z=0.5这个值比较靠经验)
|
||||
const stdVector = new THREE.Vector3(x1, y1, 0.5);
|
||||
//世界坐标
|
||||
const worldVector = stdVector.unproject(camera);
|
||||
console.log(worldVector);
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
// 获取raycaster直线和所有模型相交的数组集合
|
||||
var intersects = raycaster.intersectObjects(interactableMeshes);
|
||||
if (intersects.length > 0) {
|
||||
console.log(intersects[0].object);
|
||||
}
|
||||
}
|
||||
window.addEventListener('click', onMouseClick, false);
|
||||
}
|
||||
|
||||
handleToggle = () => {
|
||||
this.scene.remove(this.state.showAnimal ? this.animalCell : this.plantCell);
|
||||
this.scene.add(this.state.showAnimal ? this.plantCell : this.animalCell);
|
||||
this.scene.remove(this.state.showAnimal ? this.animalGroup : this.plantGroup);
|
||||
this.scene.add(this.state.showAnimal ? this.plantGroup : this.animalGroup);
|
||||
this.setState({
|
||||
showAnimal: !this.state.showAnimal
|
||||
})
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 711 KiB After Width: | Height: | Size: 225 KiB |
|
|
@ -5,6 +5,7 @@
|
|||
color: #FFFFFF;
|
||||
font-size: 56px;
|
||||
top: 10%;
|
||||
letter-spacing: .1em;
|
||||
text-shadow: 0 1px 0 hsl(174,5%,80%),
|
||||
0 2px 0 hsl(174,5%,75%),
|
||||
0 3px 0 hsl(174,5%,70%),
|
||||
|
|
@ -36,8 +37,7 @@
|
|||
background: url('./images/3d.png') no-repeat center;
|
||||
background-size: 100% 100%;
|
||||
filter: drop-shadow(0px 0px 8px rgba(0, 0, 0, .5));
|
||||
transition: all .5s linear;
|
||||
opacity: .85;
|
||||
transition: all .4s ease-in-out;
|
||||
}
|
||||
|
||||
.grid_item:hover .three_logo {
|
||||
|
|
|
|||
|
|
@ -24,56 +24,56 @@ const Item = styled(Paper)(({ theme }) => ({
|
|||
const workList = [
|
||||
{
|
||||
link: '#/city',
|
||||
title: '🏙 数字城市',
|
||||
description: 'Lizards are a widespread group of squamate reptiles.',
|
||||
title: '数字城市',
|
||||
description: '🏙 3D数字城市',
|
||||
image: cityImage,
|
||||
three: true
|
||||
},
|
||||
{
|
||||
link: 'https://dragonir.github.io/3d-meta-logo/',
|
||||
title: '🪐 脸书Meta元宇宙Logo',
|
||||
description: 'Lizards are a widespread group of squamate reptiles.',
|
||||
title: '脸书Meta元宇宙Logo',
|
||||
description: '🪐 Three.js + Blender 实现炫酷的Facebook元宇宙Logo.',
|
||||
image: metaImage,
|
||||
three: true
|
||||
},
|
||||
{
|
||||
link: 'https://dragonir.github.io/3d-panoramic-vision/',
|
||||
title: '🕵️ 全景侦探小游戏',
|
||||
description: '使用Three.js全景功能实现侦探小游戏。',
|
||||
title: '全景侦探小游戏',
|
||||
description: '🕵️ 使用Three.js全景功能实现侦探小游戏。',
|
||||
image: panoramicImage,
|
||||
three: true
|
||||
},
|
||||
{
|
||||
link: '#/earth',
|
||||
title: '🌏 Low Poly地球',
|
||||
description: 'Lizards are a widespread group of squamate reptiles.',
|
||||
title: '地球',
|
||||
description: '🌏 尽情探索3D Low Poly数字地球吧!',
|
||||
image: earthImage,
|
||||
three: true
|
||||
},
|
||||
{
|
||||
link: '#/cell',
|
||||
title: '👻 细胞',
|
||||
description: '可以查看动物细胞和植物细胞的内部组成结构。',
|
||||
title: '动植物细胞结构',
|
||||
description: '👻 可以查看动物细胞和植物细胞的内部组成结构。',
|
||||
image: cellImage,
|
||||
three: true
|
||||
},
|
||||
{
|
||||
link: '#/lunar',
|
||||
title: '🐅 虎年春节创意页面',
|
||||
description: 'Lizards are a widespread group of squamate reptiles.',
|
||||
title: '虎年春节创意',
|
||||
description: '🐅 2022虎虎生威!',
|
||||
image: lunarImage,
|
||||
three: true
|
||||
},
|
||||
{
|
||||
link: 'https://dragonir.github.io/zelda-map/',
|
||||
title: 'Zelda地图',
|
||||
description: 'Lizards are a widespread group of squamate reptiles.',
|
||||
title: '塞尔达:旷野之息地图',
|
||||
description: '🗺 在地图上标记神庙、查询回忆点!',
|
||||
image: zeldaImage,
|
||||
},
|
||||
{
|
||||
link: 'https://dragonir.github.io/h5-scan-qrcode/',
|
||||
title: '扫码',
|
||||
description: 'Lizards are a widespread group of squamate reptiles.',
|
||||
title: '浏览器扫码',
|
||||
description: '📷 使用原生浏览器就可以在h5页面实现扫码功能了,试试看!',
|
||||
image: scanImage,
|
||||
}
|
||||
];
|
||||
|
|
@ -83,7 +83,7 @@ export default class Home extends React.Component {
|
|||
return (
|
||||
<div className="home" style={{ padding: '24px'}}>
|
||||
<Box>
|
||||
<h1 className="page_title">MY WORK LIST</h1>
|
||||
<h1 className="page_title">dragonir's work list</h1>
|
||||
</Box>
|
||||
<Box sx={{ width: '100%' }} style={{ maxWidth: '1200px', margin: 'auto' }}>
|
||||
<Grid container rowSpacing={1} columnSpacing={{ xs: 1, sm: 2, md: 3 }}>
|
||||
|
|
|
|||