| <!DOCTYPE html> |
| <html lang="en"> |
| |
| <head> |
| <meta charset="utf-8"> |
| <title>three.js webgl - interactive - voxel painter</title> |
| <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> |
| <link type="text/css" rel="stylesheet" href="main.css"> |
| <style> |
| body { |
| background-color: #f0f0f0; |
| color: #444; |
| } |
| |
| a { |
| color: #08f; |
| } |
| </style> |
| </head> |
| |
| <body> |
| |
| <div id="info"> |
| <a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - voxel painter - webgl<br> |
| <strong>click</strong>: add voxel, <strong>shift + click</strong>: remove voxel |
| </div> |
| |
| |
| |
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> |
| |
| <script type="importmap"> |
| { |
| "imports": { |
| "three": "../build/three.module.js", |
| "three/addons/": "./jsm/" |
| } |
| } |
| </script> |
| |
| <script type="module"> |
| |
| import * as THREE from 'three'; |
| |
| let camera, scene, renderer; |
| let plane; |
| let pointer, raycaster, isShiftDown = false; |
| |
| let rollOverMesh, rollOverMaterial; |
| let cubeGeo, cubeMaterial; |
| |
| |
| const objects = []; |
| |
| init(); |
| render(); |
| |
| function init() { |
| |
| camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000); |
| camera.position.set(500, 800, 1300); |
| camera.lookAt(0, 0, 0); |
| |
| scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0xf0f0f0); |
| |
| |
| |
| const rollOverGeo = new THREE.BoxGeometry(50, 50, 50); |
| rollOverMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, opacity: 0.5, transparent: true }); |
| rollOverMesh = new THREE.Mesh(rollOverGeo, rollOverMaterial); |
| scene.add(rollOverMesh); |
| |
| |
| |
| cubeGeo = new THREE.BoxGeometry(50, 50, 50); |
| cubeMaterial = new THREE.MeshLambertMaterial({ color: 0xfeb74c, map: new THREE.TextureLoader().load('textures/square-outline-textured.png') }); |
| |
| |
| |
| const gridHelper = new THREE.GridHelper(1000, 20); |
| scene.add(gridHelper); |
| |
| |
| |
| raycaster = new THREE.Raycaster(); |
| pointer = new THREE.Vector2(); |
| |
| const geometry = new THREE.PlaneGeometry(1000, 1000); |
| geometry.rotateX(- Math.PI / 2); |
| |
| plane = new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ visible: false })); |
| scene.add(plane); |
| |
| objects.push(plane); |
| |
| |
| |
| const ambientLight = new THREE.AmbientLight(0x606060); |
| scene.add(ambientLight); |
| |
| const directionalLight = new THREE.DirectionalLight(0xffffff); |
| directionalLight.position.set(1, 0.75, 0.5).normalize(); |
| scene.add(directionalLight); |
| |
| renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setPixelRatio(window.devicePixelRatio); |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| document.body.appendChild(renderer.domElement); |
| |
| document.addEventListener('pointermove', onPointerMove); |
| document.addEventListener('pointerdown', onPointerDown); |
| document.addEventListener('keydown', onDocumentKeyDown); |
| document.addEventListener('keyup', onDocumentKeyUp); |
| |
| |
| |
| window.addEventListener('resize', onWindowResize); |
| |
| } |
| |
| function onWindowResize() { |
| |
| camera.aspect = window.innerWidth / window.innerHeight; |
| camera.updateProjectionMatrix(); |
| |
| renderer.setSize(window.innerWidth, window.innerHeight); |
| |
| render(); |
| |
| } |
| |
| function onPointerMove(event) { |
| |
| pointer.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1); |
| |
| raycaster.setFromCamera(pointer, camera); |
| |
| const intersects = raycaster.intersectObjects(objects, false); |
| |
| if (intersects.length > 0) { |
| |
| |
| const intersect = intersects[0]; |
| |
| rollOverMesh.position.copy(intersect.point).add(intersect.face.normal); |
| rollOverMesh.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25); |
| |
| render(); |
| |
| } |
| |
| } |
| |
| function onPointerDown(event) { |
| |
| pointer.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1); |
| raycaster.setFromCamera(pointer, camera); |
| const intersects = raycaster.intersectObjects(objects, false); |
| |
| if (intersects.length > 0) { |
| |
| const intersect = intersects[0]; |
| |
| |
| if (isShiftDown) { |
| |
| if (intersect.object !== plane) { |
| |
| |
| scene.remove(intersect.object); |
| objects.splice(objects.indexOf(intersect.object), 1); |
| |
| } |
| |
| |
| |
| } else { |
| |
| const voxel = new THREE.Mesh(cubeGeo, cubeMaterial); |
| voxel.position.copy(intersect.point).add(intersect.face.normal); |
| voxel.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25); |
| scene.add(voxel); |
| |
| objects.push(voxel); |
| |
| } |
| |
| render(); |
| |
| } |
| |
| } |
| |
| function onDocumentKeyDown(event) { |
| |
| switch (event.keyCode) { |
| |
| case 16: isShiftDown = true; break; |
| |
| } |
| |
| } |
| |
| function onDocumentKeyUp(event) { |
| |
| switch (event.keyCode) { |
| |
| case 16: isShiftDown = false; break; |
| |
| } |
| |
| } |
| |
| function render() { |
| |
| renderer.render(scene, camera); |
| |
| } |
| |
| </script> |
| |
| </body> |
| |
| </html> |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战