270 lines
7.7 KiB
JavaScript
270 lines
7.7 KiB
JavaScript
import * as THREE from "three";
|
|
|
|
const STAR_POINTS = 5;
|
|
|
|
let scene, camera, renderer;
|
|
let coreStarGroup;
|
|
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 spotLight2 = new THREE.PointLight(0xffffff, 0.8);
|
|
spotLight2.position.set(-10, 0, 0);
|
|
spotLight2.castShadow = true;
|
|
spotLight2.shadow.mapSize.width = 1024; // default
|
|
spotLight2.shadow.mapSize.height = 1024; // default
|
|
scene.add(spotLight2);
|
|
|
|
let ambient = new THREE.AmbientLight(0x404040);
|
|
scene.add(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};
|