mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-13 17:35:40 +02:00
wip
This commit is contained in:
BIN
packages/frontend/assets/world/chars.af
Normal file
BIN
packages/frontend/assets/world/chars.af
Normal file
Binary file not shown.
BIN
packages/frontend/assets/world/chars.png
Normal file
BIN
packages/frontend/assets/world/chars.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Binary file not shown.
Binary file not shown.
@@ -8,7 +8,7 @@ import { AxesViewer } from '@babylonjs/core/Debug/axesViewer';
|
||||
import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { HorizontalCameraKeyboardMoveInput, WORLD_SCALE, camelToKebab, cm, createPlaneUvMapper, normalizeUvToSquare, randomRange } from './utility.js';
|
||||
import { HorizontalCameraKeyboardMoveInput, RecyvlingTextGrid, WORLD_SCALE, camelToKebab, cm, createPlaneUvMapper, normalizeUvToSquare, randomRange } from './utility.js';
|
||||
import { TIME_MAP } from './utility.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
@@ -41,6 +41,7 @@ export class WorldEngine extends EventEmitter<WorldEngineEvents> {
|
||||
public lightContainer: BABYLON.ClusteredLightContainer;
|
||||
public sr: BABYLON.SnapshotRenderingHelper;
|
||||
private gl: BABYLON.GlowLayer | null = null;
|
||||
public textMaterial: BABYLON.StandardMaterial;
|
||||
|
||||
public isSitting = false;
|
||||
private fps: number | null = null;
|
||||
@@ -220,6 +221,38 @@ export class WorldEngine extends EventEmitter<WorldEngineEvents> {
|
||||
if (mesh.material) (mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.envMap;
|
||||
}
|
||||
|
||||
this.textMaterial = new BABYLON.StandardMaterial('textMaterial', this.scene);
|
||||
this.textMaterial.diffuseTexture = new BABYLON.Texture('/client-assets/world/chars.png', this.scene, false, false);
|
||||
this.textMaterial.diffuseTexture.hasAlpha = true;
|
||||
this.textMaterial.disableLighting = true;
|
||||
this.textMaterial.transparencyMode = BABYLON.Material.MATERIAL_ALPHABLEND;
|
||||
this.textMaterial.useAlphaFromDiffuseTexture = true;
|
||||
this.textMaterial.freeze();
|
||||
|
||||
{
|
||||
const messageRingRoot = new BABYLON.TransformNode('', this.scene);
|
||||
const messageRing = envObj.meshes.find(m => m.name.includes('__MESSAGE_RING__'));
|
||||
messageRing.parent = messageRingRoot;
|
||||
messageRing.rotation = messageRing.rotationQuaternion.toEulerAngles();
|
||||
messageRing.rotationQuaternion = null;
|
||||
const text = new RecyvlingTextGrid(messageRing, 256, {
|
||||
dir: 'left',
|
||||
material: this.textMaterial,
|
||||
});
|
||||
|
||||
text.write('Wellcome to Misskey World!');
|
||||
|
||||
//messageRingRoot.rotation.x = Math.PI / 4;
|
||||
|
||||
const anim = new BABYLON.Animation('', 'rotation.y', 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
|
||||
anim.setKeys([
|
||||
{ frame: 0, value: 0 },
|
||||
{ frame: 10000, value: Math.PI * 2 },
|
||||
]);
|
||||
messageRing.animations = [anim];
|
||||
this.scene.beginAnimation(messageRing, 0, 10000, true);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 16; i++) {
|
||||
const sphereRoot = new BABYLON.TransformNode('', this.scene);
|
||||
sphereRoot.position = new BABYLON.Vector3(cm(0), cm(1000 + (100 * i)), cm(0));
|
||||
@@ -302,6 +335,8 @@ export class WorldEngine extends EventEmitter<WorldEngineEvents> {
|
||||
const worldRingH = envObj.meshes.find(m => m.name.includes('__WORLD_RING_H__'));
|
||||
const worldRingM = envObj.meshes.find(m => m.name.includes('__WORLD_RING_M__'));
|
||||
|
||||
worldRingH.rotation = worldRingH.rotationQuaternion.toEulerAngles();
|
||||
worldRingM.rotation = worldRingM.rotationQuaternion.toEulerAngles();
|
||||
worldRingH.rotationQuaternion = null;
|
||||
worldRingM.rotationQuaternion = null;
|
||||
|
||||
@@ -312,11 +347,8 @@ export class WorldEngine extends EventEmitter<WorldEngineEvents> {
|
||||
|
||||
setInterval(() => {
|
||||
const time = Date.now();
|
||||
worldRingH.rotation.x = ((time % _30days) / _30days) * Math.PI * 2;
|
||||
worldRingH.rotation.z = ((time % _12h) / _12h) * Math.PI * 2;
|
||||
|
||||
worldRingM.rotation.x = ((time % _7days) / _7days) * Math.PI * 2;
|
||||
worldRingM.rotation.z = ((time % _1h) / _1h) * Math.PI * 2;
|
||||
worldRingH.rotation.x = ((time % _12h) / _12h) * Math.PI * 2;
|
||||
worldRingM.rotation.y = -(((time % _1h) / _1h) * Math.PI * 2);
|
||||
}, 100);
|
||||
|
||||
const screenMeshes = envObj.meshes.filter(m => m.name.includes('__SCREEN__'));
|
||||
|
||||
@@ -397,3 +397,209 @@ export function getMeshesBoundingBox(meshes: BABYLON.Mesh[]): BABYLON.BoundingBo
|
||||
export function randomRange(min: number, max: number) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
export function remap(value: number, inMin: number, inMax: number, outMin: number, outMax: number) {
|
||||
return outMin + (outMax - outMin) * ((value - inMin) / (inMax - inMin));
|
||||
}
|
||||
|
||||
const TEXT_TEXTURE_CHAR_COLS = 16;
|
||||
const TEXT_TEXTURE_CHAR_ROWS = 16;
|
||||
|
||||
const TEXT_TEXTURE_CHAR_MAP = {
|
||||
'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, ' ': 26,
|
||||
'a': 32, 'b': 33, 'c': 34, 'd': 35, 'e': 36, 'f': 37, 'g': 38, 'h': 39, 'i': 40, 'j': 41, 'k': 42, 'l': 43, 'm': 44, 'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49, 's': 50, 't': 51, 'u': 52, 'v': 53, 'w': 54, 'x': 55, 'y': 56, 'z': 57,
|
||||
'0': 64, '1': 65, '2': 66, '3': 67, '4': 68, '5': 69, '6': 70, '7': 71, '8': 72, '9': 73,
|
||||
'!': 78, '?': 79, '+': 80,
|
||||
'■': 255,
|
||||
};
|
||||
|
||||
const TEXT_TEXTURE_CHAR_WIDTH_MAP = {
|
||||
'A': 0.7, 'B': 0.7, 'C': 0.7, 'D': 0.7, 'E': 0.7, 'F': 0.7, 'G': 0.7, 'H': 0.7, 'I': 0.4, 'J': 0.6, 'K': 0.7, 'L': 0.6, 'M': 0.8, 'N': 0.7, 'O': 0.7, 'P': 0.7, 'Q': 0.7, 'R': 0.7, 'S': 0.7, 'T': 0.7, 'U': 0.7, 'V': 0.7, 'W': 0.9, 'X': 0.7, 'Y': 0.7, 'Z': 0.7, ' ': 0.4,
|
||||
'a': 0.6, 'b': 0.6, 'c': 0.6, 'd': 0.6, 'e': 0.6, 'f': 0.4, 'g': 0.6, 'h': 0.6, 'i': 0.3, 'j': 0.3, 'k': 0.6, 'l': 0.3, 'm': 0.9, 'n': 0.6, 'o': 0.6, 'p': 0.6, 'q': 0.6, 'r': 0.4, 's': 0.6, 't': 0.4, 'u': 0.6, 'v': 0.6, 'w': 0.8, 'x': 0.6, 'y': 0.6, 'z': 0.6,
|
||||
'0': 0.6, '1': 0.6, '2': 0.6, '3': 0.6, '4': 0.6, '5': 0.6, '6': 0.6, '7': 0.6, '8': 0.6, '9': 0.6,
|
||||
'+': 0.6,
|
||||
};
|
||||
|
||||
export class RecyvlingText {
|
||||
public maxChars: number;
|
||||
public size: number;
|
||||
public dir: 'left' | 'right';
|
||||
public root: BABYLON.TransformNode;
|
||||
public meshs: BABYLON.Mesh[] = [];
|
||||
|
||||
constructor(maxChars: number, scene: BABYLON.Scene, options: {
|
||||
size: number;
|
||||
dir: 'left' | 'right';
|
||||
material: BABYLON.StandardMaterial;
|
||||
}) {
|
||||
this.maxChars = maxChars;
|
||||
this.size = options.size;
|
||||
this.dir = options.dir;
|
||||
|
||||
this.root = new BABYLON.TransformNode('textMeshsGroup', scene);
|
||||
|
||||
for (let i = 0; i < maxChars; i++) {
|
||||
const plane = BABYLON.MeshBuilder.CreatePlane('plane', {
|
||||
size: options.size,
|
||||
sideOrientation: BABYLON.Mesh.DOUBLESIDE,
|
||||
updatable: true,
|
||||
}, scene);
|
||||
plane.material = options.material;
|
||||
plane.parent = this.root;
|
||||
this.meshs.push(plane);
|
||||
}
|
||||
|
||||
this.write('');
|
||||
}
|
||||
|
||||
public write(text: string) {
|
||||
// padding text
|
||||
if (text.length < this.maxChars) {
|
||||
const padding = ' '.repeat(this.maxChars - text.length);
|
||||
text = this.dir === 'left' ? text + padding : padding + text;
|
||||
}
|
||||
|
||||
let totalWidth = 0;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = text[i];
|
||||
const charWidth = TEXT_TEXTURE_CHAR_WIDTH_MAP[char] ?? 1;
|
||||
totalWidth += this.size * charWidth;
|
||||
}
|
||||
|
||||
let xPos = 0;
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
const char = this.dir === 'left' ? text[i] : text[text.length - i - 1];
|
||||
const index = TEXT_TEXTURE_CHAR_MAP[char];
|
||||
const charWidth = TEXT_TEXTURE_CHAR_WIDTH_MAP[char] ?? 1;
|
||||
const x = index % TEXT_TEXTURE_CHAR_COLS;
|
||||
const y = Math.floor(index / TEXT_TEXTURE_CHAR_COLS);
|
||||
|
||||
if (this.dir === 'left') {
|
||||
xPos += (this.size * charWidth);
|
||||
} else if (this.dir === 'right') {
|
||||
xPos -= (this.size * charWidth);
|
||||
}
|
||||
|
||||
const plane = this.meshs[i];
|
||||
const uvs = plane.getVerticesData(BABYLON.VertexBuffer.UVKind);
|
||||
uvs[0] = uvs[6] = x / TEXT_TEXTURE_CHAR_COLS;
|
||||
uvs[1] = uvs[3] = (y + 1) / TEXT_TEXTURE_CHAR_ROWS;
|
||||
uvs[2] = uvs[4] = (x + 1) / TEXT_TEXTURE_CHAR_COLS;
|
||||
uvs[5] = uvs[7] = y / TEXT_TEXTURE_CHAR_ROWS;
|
||||
plane.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
|
||||
plane.position = new BABYLON.Vector3(xPos, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public getMeshAt(index: number) {
|
||||
return this.meshs[index];
|
||||
}
|
||||
}
|
||||
|
||||
export class RecyvlingTextGrid {
|
||||
public facesCount: number;
|
||||
public mesh: BABYLON.Mesh;
|
||||
private uvs: BABYLON.FloatArray;
|
||||
|
||||
constructor(mesh: BABYLON.Mesh, facesCount: number, options: {
|
||||
material: BABYLON.StandardMaterial;
|
||||
}) {
|
||||
this.mesh = mesh;
|
||||
this.mesh.material = options.material;
|
||||
this.mesh.convertToUnIndexedMesh();
|
||||
this.mesh.markVerticesDataAsUpdatable(BABYLON.VertexBuffer.UVKind, true);
|
||||
|
||||
this.facesCount = facesCount;
|
||||
this.uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!;
|
||||
|
||||
//this.write('');
|
||||
}
|
||||
|
||||
public write(text: string) {
|
||||
const charIndexes: number[] = [];
|
||||
|
||||
const repeatSeparator = ' ■ ';
|
||||
let maxRepeat = Math.ceil(this.facesCount / text.length);
|
||||
if (maxRepeat > 1) {
|
||||
text += repeatSeparator;
|
||||
maxRepeat = Math.ceil(this.facesCount / text.length);
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.facesCount; i++) {
|
||||
if (i + text.length >= (maxRepeat * text.length)) {
|
||||
if (i >= this.facesCount - repeatSeparator.length) {
|
||||
charIndexes.push(TEXT_TEXTURE_CHAR_MAP[repeatSeparator[(i - (this.facesCount - repeatSeparator.length)) % repeatSeparator.length]]);
|
||||
} else {
|
||||
charIndexes.push(TEXT_TEXTURE_CHAR_MAP[' ']);
|
||||
}
|
||||
continue;
|
||||
} else if (i >= text.length) {
|
||||
const char = text[i % text.length];
|
||||
const index = TEXT_TEXTURE_CHAR_MAP[char] ?? TEXT_TEXTURE_CHAR_MAP['■'];
|
||||
charIndexes.push(index);
|
||||
continue;
|
||||
}
|
||||
|
||||
const char = text[i];
|
||||
const index = TEXT_TEXTURE_CHAR_MAP[char] ?? TEXT_TEXTURE_CHAR_MAP['■'];
|
||||
charIndexes.push(index);
|
||||
}
|
||||
|
||||
const uvs = this.uvs;
|
||||
|
||||
const verticesCountPerFace = 6; // ひとつの四角はふたつの三角に分割されるので 3*2=6
|
||||
|
||||
for (let i = 0; i < this.facesCount; i++) {
|
||||
const charIndex = charIndexes[i];
|
||||
const charX = charIndex % TEXT_TEXTURE_CHAR_COLS;
|
||||
const charY = Math.floor(charIndex / TEXT_TEXTURE_CHAR_COLS);
|
||||
|
||||
const uvIndex = i * (verticesCountPerFace * 2); // uvは(x,y)の2要素なので*2
|
||||
|
||||
/*
|
||||
a--b d
|
||||
| / / |
|
||||
c e--f
|
||||
*/
|
||||
let aIndex = 0;
|
||||
let bIndex = 0;
|
||||
let cIndex = 0;
|
||||
let dIndex = 0;
|
||||
let eIndex = 0;
|
||||
let fIndex = 0;
|
||||
|
||||
for (let j = 0; j < (verticesCountPerFace * 2); j += 2) {
|
||||
const x = uvs[uvIndex + j];
|
||||
const y = uvs[uvIndex + j + 1];
|
||||
|
||||
// 多少ずれがあってもいいように(例えばblenderではUV展開時にデフォルトでわずかなマージンを追加する)、中心より大きいか/小さいかで判定する
|
||||
// ひとつの四角はふたつの三角に分割される。右下に来る三角(d-e-f)の方が先にくるっぽい
|
||||
if (j >= 6) {
|
||||
if (x < 0.5 && y < 0.5) {
|
||||
aIndex = j;
|
||||
} else if (x > 0.5 && y < 0.5) {
|
||||
bIndex = j;
|
||||
} else if (x < 0.5 && y > 0.5) {
|
||||
cIndex = j;
|
||||
}
|
||||
} else {
|
||||
if (x > 0.5 && y < 0.5) {
|
||||
dIndex = j;
|
||||
} else if (x < 0.5 && y > 0.5) {
|
||||
eIndex = j;
|
||||
} else if (x > 0.5 && y > 0.5) {
|
||||
fIndex = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uvs[uvIndex + aIndex + 0] = uvs[uvIndex + cIndex + 0] = uvs[uvIndex + eIndex + 0] = charX / TEXT_TEXTURE_CHAR_COLS;
|
||||
uvs[uvIndex + aIndex + 1] = uvs[uvIndex + bIndex + 1] = uvs[uvIndex + dIndex + 1] = charY / TEXT_TEXTURE_CHAR_ROWS;
|
||||
uvs[uvIndex + bIndex + 0] = uvs[uvIndex + dIndex + 0] = uvs[uvIndex + fIndex + 0] = (charX + 1) / TEXT_TEXTURE_CHAR_COLS;
|
||||
uvs[uvIndex + cIndex + 1] = uvs[uvIndex + eIndex + 1] = uvs[uvIndex + fIndex + 1] = (charY + 1) / TEXT_TEXTURE_CHAR_ROWS;
|
||||
}
|
||||
|
||||
this.mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user