386 lines
10 KiB
TypeScript
386 lines
10 KiB
TypeScript
import * as THREE from "three";
|
|
import { BackgroundProp } from "./BackgroundSelector";
|
|
import { FontLoader } from "three/addons/loaders/FontLoader.js";
|
|
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
|
|
import { ShadowMapViewer } from "three/addons/utils/ShadowMapViewer.js";
|
|
|
|
THREE.Cache.enabled = true;
|
|
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
|
import { ColorGradientDir, TextProp } from "./TextSetting";
|
|
import { EffectProp } from "./Effects";
|
|
|
|
let camera: THREE.PerspectiveCamera,
|
|
scene: THREE.Scene,
|
|
renderer: THREE.WebGLRenderer,
|
|
controls: OrbitControls,
|
|
container: HTMLCanvasElement;
|
|
export function init(
|
|
_container: HTMLCanvasElement,
|
|
width: number,
|
|
height: number
|
|
) {
|
|
container = _container;
|
|
scene = new THREE.Scene();
|
|
|
|
renderer = new THREE.WebGLRenderer({
|
|
antialias: true,
|
|
alpha: true,
|
|
canvas: container,
|
|
});
|
|
renderer.setPixelRatio(1);
|
|
renderer.setSize(width, height, false);
|
|
renderer.setAnimationLoop(animate);
|
|
renderer.shadowMap.enabled = true;
|
|
|
|
camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
|
|
camera.position.set(0, 0, 50);
|
|
|
|
// const domWidth = container.clientWidth;
|
|
// const domHeight = container.clientHeight;
|
|
|
|
// camera = new THREE.OrthographicCamera(
|
|
// domWidth / -2,
|
|
// domWidth / 2,
|
|
// domHeight / 2,
|
|
// domHeight / -2,
|
|
// 0.1,
|
|
// 1000
|
|
// );
|
|
|
|
// camera.position.set(0, 10, 0);
|
|
|
|
// controls
|
|
|
|
controls = new OrbitControls(camera, renderer.domElement);
|
|
// controls.screenSpacePanning = false;
|
|
|
|
controls.enabled = true;
|
|
// controls.enablePan = false;
|
|
|
|
//controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
|
|
|
|
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
|
|
controls.dampingFactor = 0.05;
|
|
|
|
controls.screenSpacePanning = false;
|
|
|
|
controls.minDistance = 0.1;
|
|
controls.maxDistance = 50000;
|
|
|
|
// lights
|
|
|
|
const dirLight1 = new THREE.DirectionalLight(0xffffff, 3);
|
|
dirLight1.position.set(1, 1, 1);
|
|
scene.add(dirLight1);
|
|
|
|
const dirLight2 = new THREE.DirectionalLight(0x002288, 3);
|
|
dirLight2.position.set(-10, 10, 50);
|
|
dirLight2.castShadow = true;
|
|
dirLight2.shadow.camera.left = -100;
|
|
dirLight2.shadow.camera.top = 100;
|
|
dirLight2.shadow.camera.bottom = -100;
|
|
dirLight2.shadow.camera.right = 100;
|
|
dirLight2.shadow.camera.near = 0;
|
|
dirLight2.shadow.camera.far = 200;
|
|
dirLight2.shadow.bias = -0.000222;
|
|
dirLight2.shadow.mapSize.width = 2048;
|
|
dirLight2.shadow.mapSize.height = 2048;
|
|
scene.add(dirLight2);
|
|
|
|
// const helper = new THREE.DirectionalLightHelper(dirLight2, 50);
|
|
// scene.add(helper);
|
|
|
|
// const helper2 = new THREE.CameraHelper(dirLight2.shadow.camera);
|
|
// scene.add(helper2);
|
|
|
|
const ambientLight = new THREE.AmbientLight(0x555555);
|
|
scene.add(ambientLight);
|
|
}
|
|
|
|
function animate() {
|
|
controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
|
|
render();
|
|
}
|
|
|
|
function render() {
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
export function resize(
|
|
width: number,
|
|
height: number,
|
|
clientWidth: number,
|
|
clientHeight: number
|
|
) {
|
|
console.log("resize to width = " + width + " height = " + height);
|
|
|
|
camera.aspect = width / height;
|
|
camera.updateProjectionMatrix();
|
|
|
|
// camera = new THREE.OrthographicCamera(
|
|
// clientWidth / -2,
|
|
// clientWidth / 2,
|
|
// clientHeight / 2,
|
|
// clientHeight / -2,
|
|
// 0.1,
|
|
// 1000
|
|
// );
|
|
|
|
renderer.setSize(width, height, false);
|
|
}
|
|
|
|
let textMesh: THREE.Mesh;
|
|
let lastTextProps: TextProp | null = null;
|
|
export async function updateTextProp(textProps: TextProp) {
|
|
// const mirror = true;
|
|
// const plane = new THREE.Mesh(
|
|
// new THREE.PlaneGeometry(10000, 10000),
|
|
// new THREE.MeshBasicMaterial({
|
|
// color: 0xffffff,
|
|
// opacity: 0.5,
|
|
// transparent: true,
|
|
// })
|
|
// );
|
|
// plane.position.y = 100;
|
|
// plane.rotation.x = -Math.PI / 2;
|
|
// scene.add(plane);
|
|
|
|
if (lastTextProps == null) {
|
|
const geo = await getTextGeometry(textProps);
|
|
const mat = new THREE.MeshLambertMaterial({
|
|
side: THREE.DoubleSide,
|
|
});
|
|
|
|
if (Array.isArray(textProps.color)) {
|
|
// 渐变颜色处理
|
|
setGradient(textProps.color, textProps.colorGradientDir, geo, mat);
|
|
} else {
|
|
// 单色处理
|
|
setColor(textProps.color, mat);
|
|
}
|
|
let size = new THREE.Vector3();
|
|
geo.boundingBox?.getSize(size);
|
|
let textMesh1 = new THREE.Mesh(geo, mat);
|
|
// textMesh1.rotateX(-Math.PI / 2);
|
|
textMesh1.scale.multiplyScalar(50).divideScalar(size.x);
|
|
textMesh1.castShadow = true;
|
|
textMesh1.receiveShadow = true;
|
|
|
|
scene.add(textMesh1);
|
|
|
|
textMesh = textMesh1;
|
|
|
|
lastTextProps = textProps;
|
|
|
|
return;
|
|
}
|
|
|
|
if (needUpdateGeo(textProps)) {
|
|
let geo = await getTextGeometry(textProps);
|
|
textMesh.geometry.dispose();
|
|
textMesh.geometry = geo;
|
|
let size = new THREE.Vector3();
|
|
geo.boundingBox?.getSize(size);
|
|
textMesh.scale.set(1, 1, 1).multiplyScalar(50).divideScalar(size.x);
|
|
|
|
if (Array.isArray(textProps.color)) {
|
|
setGradient(
|
|
textProps.color,
|
|
textProps.colorGradientDir,
|
|
textMesh.geometry,
|
|
textMesh.material as THREE.MeshLambertMaterial
|
|
);
|
|
}
|
|
} else {
|
|
if (Array.isArray(textProps.color)) {
|
|
setGradient(
|
|
textProps.color,
|
|
textProps.colorGradientDir,
|
|
textMesh.geometry,
|
|
textMesh.material as THREE.MeshLambertMaterial
|
|
);
|
|
} else {
|
|
// 单色处理
|
|
setColor(textProps.color, textMesh.material as THREE.MeshLambertMaterial);
|
|
}
|
|
}
|
|
|
|
scene.add(textMesh);
|
|
lastTextProps = textProps;
|
|
}
|
|
|
|
function needUpdateGeo(textProps: TextProp) {
|
|
return (
|
|
lastTextProps?.text != textProps.text ||
|
|
lastTextProps?.font != textProps.font ||
|
|
lastTextProps?.weight != textProps.weight
|
|
);
|
|
}
|
|
|
|
function setGradient(
|
|
colors: string[],
|
|
dir: ColorGradientDir,
|
|
geo: THREE.BufferGeometry,
|
|
mat: THREE.MeshLambertMaterial
|
|
) {
|
|
// 渐变颜色处理
|
|
mat.vertexColors = true;
|
|
mat.needsUpdate = true;
|
|
mat.color.set(1, 1, 1);
|
|
const startColor = new THREE.Color(colors[0]);
|
|
const endColor = new THREE.Color(colors[1]);
|
|
|
|
const colorss = [];
|
|
const position = geo.attributes.position;
|
|
|
|
if (dir == "l2r") {
|
|
const maxX = geo.boundingBox!.max.x;
|
|
const minX = geo.boundingBox!.min.x;
|
|
const color = new THREE.Color();
|
|
for (let i = 0; i < position.count; i++) {
|
|
const x = position.getX(i);
|
|
const t = (x - minX) / (maxX - minX); // 归一化
|
|
color.lerpColors(startColor, endColor, t);
|
|
colorss.push(color.r, color.g, color.b);
|
|
}
|
|
} else if (dir == "t2b") {
|
|
const maxY = geo.boundingBox!.max.y;
|
|
const minY = geo.boundingBox!.min.y;
|
|
const color = new THREE.Color();
|
|
|
|
for (let i = 0; i < position.count; i++) {
|
|
const y = position.getY(i);
|
|
const t = (y - minY) / (maxY - minY); // 归一化
|
|
color.lerpColors(startColor, endColor, 1.0 - t);
|
|
colorss.push(color.r, color.g, color.b);
|
|
}
|
|
}
|
|
|
|
geo.setAttribute(
|
|
"color",
|
|
new THREE.Float32BufferAttribute(new Float32Array(colorss), 3)
|
|
);
|
|
geo.attributes.color.needsUpdate = true;
|
|
}
|
|
|
|
function setColor(color: string, mat: THREE.MeshLambertMaterial) {
|
|
// 渐变颜色处理
|
|
mat.vertexColors = false;
|
|
mat.color.set(color);
|
|
mat.needsUpdate = true;
|
|
}
|
|
|
|
async function getTextGeometry(textProps: TextProp) {
|
|
let text = textProps.text;
|
|
let bevelEnabled = true;
|
|
let font = await loadFont(textProps);
|
|
const depth = 20,
|
|
size = 70,
|
|
// hover = 30,
|
|
curveSegments = 4,
|
|
bevelThickness = 2,
|
|
bevelSize = 1.5;
|
|
|
|
let textGeo = new TextGeometry(text, {
|
|
font,
|
|
size: size,
|
|
depth: depth,
|
|
curveSegments: curveSegments,
|
|
|
|
bevelThickness: bevelThickness,
|
|
bevelSize: bevelSize,
|
|
bevelEnabled: bevelEnabled,
|
|
});
|
|
|
|
textGeo.computeBoundingBox();
|
|
textGeo.center();
|
|
textGeo.translate(0, size / 2, depth / 2);
|
|
textGeo.computeVertexNormals();
|
|
return textGeo;
|
|
|
|
// if (mirror) {
|
|
// textMesh2 = new THREE.Mesh(textGeo, materials);
|
|
|
|
// textMesh2.position.x = centerOffset;
|
|
// textMesh2.position.y = -hover;
|
|
// textMesh2.position.z = depth;
|
|
|
|
// textMesh2.rotation.x = Math.PI;
|
|
// textMesh2.rotation.y = Math.PI * 2;
|
|
|
|
// group.add(textMesh2);
|
|
// }
|
|
}
|
|
|
|
async function loadFont(textProps: TextProp) {
|
|
const loader = new FontLoader();
|
|
let font = await loader.loadAsync(textProps.fontUrl);
|
|
return font;
|
|
}
|
|
|
|
export function updateBackground(bg: BackgroundProp) {
|
|
// if (bg.color) {
|
|
// scene.background = new THREE.Color(bg.color);
|
|
// } else {
|
|
// scene.background = null;
|
|
// }
|
|
}
|
|
|
|
// function createGradientTexture(colors: string[]) {
|
|
// const canvas = document.createElement("canvas");
|
|
// canvas.width = 256;
|
|
// canvas.height = 1;
|
|
// const ctx = canvas.getContext("2d")!;
|
|
|
|
// const gradient = ctx.createGradientGradient(0, 0, 256, 0);
|
|
// const step = 1 / (colors.length - 1);
|
|
// colors.forEach((color, i) => {
|
|
// gradient.addColorStop(i * step, color);
|
|
// });
|
|
|
|
// ctx.fillStyle = gradient;
|
|
// ctx.fillRect(0, 0, 256, 1);
|
|
|
|
// return canvas.toDataURL();
|
|
// }
|
|
|
|
export function getPicture(width: number, height: number) {
|
|
if (width == 0 || height == 0) {
|
|
render();
|
|
return container.toDataURL("image/png");
|
|
}
|
|
|
|
let lastWidth = renderer.domElement.width;
|
|
let lastHeight = renderer.domElement.height;
|
|
renderer.setSize(width, height, false);
|
|
render();
|
|
const img = container.toDataURL("image/png");
|
|
renderer.setSize(lastWidth, lastHeight, false);
|
|
return img;
|
|
}
|
|
|
|
let shadowPlane: THREE.Mesh | null = null;
|
|
export function updateEffectProp(effect: EffectProp) {
|
|
// && background.color && !background.image
|
|
if (effect.enableShadow) {
|
|
if (!shadowPlane) {
|
|
shadowPlane = new THREE.Mesh(
|
|
new THREE.PlaneGeometry(100, 100, 10, 10),
|
|
new THREE.ShadowMaterial({
|
|
color: new THREE.Color(effect.shadowColor),
|
|
opacity: 0.3,
|
|
})
|
|
);
|
|
shadowPlane.receiveShadow = true;
|
|
}
|
|
scene.add(shadowPlane);
|
|
|
|
(shadowPlane.material as THREE.ShadowMaterial).color.set(
|
|
effect.shadowColor
|
|
);
|
|
shadowPlane.visible = true;
|
|
} else {
|
|
shadowPlane && (shadowPlane.visible = false);
|
|
}
|
|
}
|