import * as THREE from "three"; const STAR_POINTS = 5; let scene, camera, renderer; let coreStarGroup; let lights = {}; let animation = { steps: 0, x: new Float32Array(4), y: new Float32Array(4), z: new Float32Array(4), }; function interpolate(range, factor) { for (let i = 1; i < range.length; ++i) { range[i] += (range[i - 1] - range[i]) * factor; } } /** * Create a BufferGeometry from vertices and faces. * * @param {Float32Array} vertices * @param {number[]} faces * @returns {BufferGeometry} */ THREE.BufferGeometry.fromVertices = function (vertices, faces) { let geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3)); geometry.setIndex(faces); geometry.computeVertexNormals(); geometry.computeBoundingBox(); geometry.computeBoundingSphere(); return geometry; }; Float32Array.prototype.last = function () { return this[this.length - 1]; }; function addStar() { let frontGeometry = THREE.BufferGeometry.fromVertices(starCoordinates(2, 3, -0.1), filledStarIndices()); let backGeometry = THREE.BufferGeometry.fromVertices(starCoordinates(2, 3, 0.1), filledStarIndices()); let material = new THREE.MeshStandardMaterial({ side: THREE.FrontSide, color: 0x4fbc32, flatShading: true, }); let ringMaterial = new THREE.MeshPhysicalMaterial({ color: 0x008000, }); let backMaterial = material.clone(); backMaterial.side = THREE.BackSide; let star = new THREE.Mesh(frontGeometry, material); star.receiveShadow = true; coreStarGroup.add(star); let backStar = new THREE.Mesh(backGeometry, backMaterial); backStar.receiveShadow = true; coreStarGroup.add(backStar); let ringGeometry = generateStarRing(2, 3, 0.4); let ring = new THREE.Mesh(ringGeometry, ringMaterial); ring.receiveShadow = ring.castShadow = true; coreStarGroup.add(ring); } /** * Concatenate a series of Float32 arrays. * * @returns {Float32Array} */ function concatFloat32Array() { let length = 0; for (let cur of arguments) { length += cur.length; } const buf = new Float32Array(length); let offset = 0; for (let cur of arguments) { buf.set(cur, offset); offset += cur.length; } return buf; } function generateStarRing(innerRadius, outerRadius, size) { let vertexCandidates = [ starCoordinates(innerRadius + 0.5 * size, outerRadius + 0.5 * size, -0.5 * size), starCoordinates(innerRadius + 0.5 * size, outerRadius + 0.5 * size, 0.5 * size), starCoordinates(innerRadius - 0.5 * size, outerRadius - 0.5 * size, 0.5 * size), starCoordinates(innerRadius - 0.5 * size, outerRadius - 0.5 * size, -0.5 * size), ]; let vertices = concatFloat32Array( vertexCandidates[0], vertexCandidates[1], vertexCandidates[2], vertexCandidates[3], vertexCandidates[1], vertexCandidates[2], vertexCandidates[3], vertexCandidates[0], ); console.log(vertices); const faces = new Array(2 * STAR_POINTS * 3 * 4); for (let i = 0; i < 4; ++i) { const offset = 2 * 2 * STAR_POINTS * i; for (let j = 0; j < 2 * STAR_POINTS; ++j) { const nextRow = (j + 1) % (2 * STAR_POINTS); faces.push(offset + j, offset + j + 2 * STAR_POINTS, offset + nextRow); faces.push(offset + j + 2 * STAR_POINTS, offset + nextRow + 2 * STAR_POINTS, offset + nextRow); } } return THREE.BufferGeometry.fromVertices(vertices, faces); } function startOpenAnimation() { scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; renderer.shadowMap.autoUpdate = true; document.body.appendChild(renderer.domElement); coreStarGroup = new THREE.Group(); scene.add(coreStarGroup); initLighting(); addStar(); loadFont(); requestAnimationFrame(animate) } function initLighting() { let spotLight = new THREE.PointLight(0xffffff); spotLight.position.set(10, 10, 10); spotLight.castShadow = true; spotLight.shadow.mapSize.width = 1024; // default spotLight.shadow.mapSize.height = 1024; // default scene.add(spotLight); let ambient = new THREE.AmbientLight(0x404040); scene.add(ambient); lights.spotLight = spotLight; lights.ambient = ambient; } /** * Load the font data and render the message. */ function loadFont() { import('three/examples/fonts/helvetiker_bold.typeface').then(function (data) { let font = new THREE.Font(data); let geometry = new THREE.TextGeometry('Ja!', { font: font, size: 1.2, height: 0.5, curveSegments: 12, bevelEnabled: true, bevelSize: 0.05, bevelThickness: 0.05 }).center(); geometry.computeFaceNormals(); let textMaterial = new THREE.MeshPhysicalMaterial({ color: 0x004f00 }); let mesh = new THREE.Mesh(geometry, textMaterial); mesh.castShadow = true; mesh.receiveShadow = true; coreStarGroup.add(mesh); }); } /** * Construct the vertex indices needed to create a filled star. * @returns {Number[]} */ function filledStarIndices() { const buf = Array(2 * STAR_POINTS * 3); const TOTAL_VERTICES = 2 * STAR_POINTS; // First, the "points" of the star. for (let i = 0; i < STAR_POINTS; ++i) { const offset = 3 * i; buf[offset] = 2 * i; buf[offset + 1] = 2 * i + 1; buf[offset + 2] = (2 * i + TOTAL_VERTICES - 1) % (TOTAL_VERTICES); } // Then, the inner part, constructed from opposing triangles. for (let i = 0; i < STAR_POINTS; ++i) { const offset = 3 * (STAR_POINTS + i); buf[offset] = 2 * i + 1; buf[offset + 1] = (2 * i + 3) % TOTAL_VERTICES; buf[offset + 2] = (2 * i + 3 + 2 * Math.floor(STAR_POINTS / 2)) % (TOTAL_VERTICES); } return buf; } /** * Generate the outer points of an N pointed star. * * @param innerRadius * @param outerRadius * @param z * @returns {Float32Array} */ function starCoordinates(innerRadius, outerRadius, z) { const buf = new Float32Array(3 * STAR_POINTS * 2); const STAR_SLICE = 2 * Math.PI / STAR_POINTS; for (let i = 0; i < STAR_POINTS; ++i) { const offset = 3 * 2 * i; buf[offset] = Math.sin(STAR_SLICE * i) * outerRadius; buf[offset + 1] = Math.cos(STAR_SLICE * i) * outerRadius; buf[offset + 3] = Math.sin(STAR_SLICE * (i + 0.5)) * innerRadius; buf[offset + 4] = Math.cos(STAR_SLICE * (i + 0.5)) * innerRadius; buf[offset + 5] = buf[offset + 2] = z; } return buf; } function animate() { requestAnimationFrame(animate); if (animation.steps === 0) { animation.steps = Math.floor(Math.random() * 60) + 30; animation.x[0] = 4 * Math.random() * Math.PI; animation.y[0] = 4 * Math.random() * Math.PI; animation.z[0] = 4 * Math.random() * Math.PI; } animation.steps--; interpolate(animation.x, 0.025); interpolate(animation.y, 0.025); interpolate(animation.z, 0.025); coreStarGroup.rotation.x = animation.x.last(); coreStarGroup.rotation.y = animation.y.last(); coreStarGroup.rotation.z = animation.z.last(); renderer.render(scene, camera); } export {startOpenAnimation};