怪物奇妙物语

宇宙无敌超级美少男的怪物奇妙物语

首页 新随笔 联系 管理
  819 随笔 :: 0 文章 :: 2 评论 :: 16万 阅读

gasp动画控制相机的位置变换

关键代码threejs/Cameras.ts

//通过gsap 动画移动相机,从而实现看似物体在运动的效果
// 1.添加动画
private changePerspectiveCameraPosition() {
// 添加一个动画
gsap.to(this.perspectiveCamera.position, {
x: 5,
z: 5,
duration: 5,
});
}
//2. 通过timeline 添加一组动画
// 参考连接: https://juejin.cn/post/7041862990622605349
private changePerspectiveCameraPositionGroup() {
const tl = gsap.timeline();
// 添加一组动画
tl.to(this.perspectiveCamera.position, {
x: 5,
z: 5,
duration: 3,
})
.to(this.perspectiveCamera.position, {
x: -5,
z: 5,
duration: 3,
})
.to(this.perspectiveCamera.position, {
x: 5,
z: 5,
duration: 3,
});
}

threejs/Cameras.ts

import { PerspectiveCamera, Scene } from "three";
import Base from "./Base";
import type Size from "./Size";
import { gsap } from "gsap";
export default class Camera {
private size: Size;
private scene: Scene;
private canvas: HTMLCanvasElement;
public perspectiveCamera: PerspectiveCamera;
constructor() {
const base = Base.getInstance();
this.size = base.size;
this.scene = base.scene;
this.canvas = base.canvas;
this.perspectiveCamera = this.createPerspectiveCamera();
this.scene.add(this.perspectiveCamera);
this.changePerspectiveCameraPositionGroup();
}
private createPerspectiveCamera() {
const perspectiveCamera = new PerspectiveCamera(
35,
this.size.aspect,
0.1,
1000
);
perspectiveCamera.position.set(0, 10, 0);
return perspectiveCamera;
}
//通过gsap 动画移动相机,从而实现看似物体在运动的效果
// 1.添加动画
private changePerspectiveCameraPosition() {
// 添加一个动画
gsap.to(this.perspectiveCamera.position, {
x: 5,
z: 5,
duration: 5,
});
}
//2. 通过timeline 添加一组动画
// 参考连接: https://juejin.cn/post/7041862990622605349
private changePerspectiveCameraPositionGroup() {
const tl = gsap.timeline();
// 添加一组动画
tl.to(this.perspectiveCamera.position, {
x: 5,
z: 5,
duration: 3,
})
.to(this.perspectiveCamera.position, {
x: -5,
z: 5,
duration: 3,
})
.to(this.perspectiveCamera.position, {
x: 5,
z: 5,
duration: 3,
});
}
public resize() {
// Updating Perspective Camera on Resize
this.perspectiveCamera.aspect = this.size.aspect;
this.perspectiveCamera.updateProjectionMatrix();
}
}
// /Users/song/Code/threejs_learn_vanilla_class_singleton/threejs_learn_vanilla_ts_class_singleton/src/threejs/Cameras.ts

threejs/Base.ts

import { Object3D, Scene } from "three";
import Camera from "./Cameras";
import Helper from "./Helpers";
import Renderer from "./Renderer";
import Size from "./Size";
import Time from "./Time";
import Control from "./Controls";
import Lights from "./Lights";
import EventManger from "./Events";
import Box from "../threejs/World/Box";
export default class Base {
public static base: Base;
public canvas: HTMLCanvasElement;
public scene: Scene;
public cameras: Camera;
public size: Size;
public time: Time;
public renderer: Renderer;
public control: Control;
public helper: Helper;
public eventManger: EventManger;
public noTransfromControlObjs: Object3D[] = [];
constructor(canvas: HTMLCanvasElement) {
Base.base = this;
this.canvas = canvas as HTMLCanvasElement;
this.scene = new Scene();
this.size = new Size();
this.cameras = new Camera();
this.time = new Time();
this.renderer = new Renderer();
new Lights();
// helper
const helper = new Helper();
this.helper = helper;
this.noTransfromControlObjs.push(helper.gridHelper, helper.axesHelper);
// control
const control = new Control();
this.control = control;
this.noTransfromControlObjs.push(this.control.transformControl);
// events
this.eventManger = new EventManger();
// 使用EventManager实例注册的事件,就是相当于全局事件,
this.eventManger.addEventListener("click", (e) => {
console.log("点击射线穿过了下面这些物体");
console.log(e);
});
// 添加box----------------------
new Box();
this.time.on("update", () => {
this.update();
});
this.size.on("resize", () => {
this.resize();
});
}
public static getInstance(canvas?: HTMLCanvasElement) {
if (canvas === undefined && Base.base !== undefined) {
return Base.base;
}
Base.base = new Base(canvas as HTMLCanvasElement);
return Base.base;
}
private resize() {
this.cameras.resize();
this.renderer.resize();
}
private update() {
this.control.update();
this.renderer.update();
}
}

threejs/Helpers.ts

import { AxesHelper, GridHelper } from "three";
import Base from "./Base";
export default class {
public axesHelper: AxesHelper;
public gridHelper: GridHelper;
constructor() {
const base = Base.getInstance();
const axesHelper = new AxesHelper(5);
this.axesHelper = axesHelper;
base.scene.add(axesHelper);
const size = 10;
const divisions = 10;
const gridHelper = new GridHelper(size, divisions);
this.gridHelper = gridHelper;
base.scene.add(gridHelper);
}
}

threejs/Lights.ts

import { AmbientLight, DirectionalLight } from "three";
import Base from "./Base";
export default class Environment {
private sunLight: DirectionalLight;
private ambientLight: AmbientLight;
constructor() {
const base = Base.getInstance();
this.sunLight = new DirectionalLight("#ffffff", 2);
this.sunLight.castShadow = true;
this.sunLight.shadow.camera.far = 20;
this.sunLight.shadow.mapSize.set(2048, 2048);
this.sunLight.shadow.normalBias = 0.05;
this.sunLight.position.set(-1.5, 7, 10);
base.scene.add(this.sunLight);
this.ambientLight = new AmbientLight("#ffffff", 0.1);
base.scene.add(this.ambientLight);
}
}

threejs/Size.ts

import EventEmitter from "eventemitter2";
export default class Size extends EventEmitter {
public width: number;
public height: number;
public aspect: number;
public pixelRatio: number;
constructor() {
super();
this.width = window.innerWidth;
this.height = window.innerHeight;
this.aspect = this.width / this.height;
this.pixelRatio = Math.min(window.devicePixelRatio, 2);
window.addEventListener("resize", () => {
this.width = window.innerWidth;
this.height = window.innerHeight;
this.aspect = this.width / this.height;
this.pixelRatio = Math.min(window.devicePixelRatio, 2);
this.emit("resize");
});
}
}

threejs/Events.ts

import {
AxesHelper,
Camera,
EventDispatcher,
GridHelper,
Line,
Object3D,
Raycaster,
Scene,
Vector2,
} from "three";
import {
TransformControlsGizmo,
TransformControlsPlane,
} from "three/examples/jsm/controls/TransformControls";
import Base from "./Base";
export default class EventManger extends EventDispatcher {
private base: Base;
private raycaster: Raycaster = new Raycaster();
private mouse: Vector2 = new Vector2();
private dom: HTMLCanvasElement;
private scene: Scene;
private camera: Camera;
constructor() {
super();
const base = Base.getInstance();
this.base = base;
this.dom = base.canvas;
this.scene = base.scene;
this.camera = base.cameras.perspectiveCamera;
const mouse = this.mouse;
const raycaster = this.raycaster;
const dom = this.dom;
let cacheObject: Object3D | null = null;
dom.addEventListener("mousedown", (event) => {
// 选取物体的操作
raycaster.setFromCamera(mouse, this.camera);
const intersection = raycaster.intersectObjects(this.scene.children);
// 这里将事件派发到this上面,相当于注册全局事件,也就是将事件派发到EventManager的实例上面
this.dispatchEvent({
type: "mousedown",
intersection,
});
if (intersection.length) {
const object = intersection[0].object;
// 派发事件到3d物体上面
object.dispatchEvent({
type: "mousedown",
});
}
});
dom.addEventListener("mousemove", (event) => {
mouse.x = (event.offsetX / dom.offsetWidth) * 2 - 1;
mouse.y = (-event.offsetY * 2) / dom.offsetHeight + 1;
// 选取物体的操作
raycaster.setFromCamera(mouse, this.camera);
const intersection = raycaster.intersectObjects(this.scene.children);
// 全局派发事件
this.dispatchEvent({
type: "mousemove",
intersection,
});
if (intersection.length) {
const object = intersection[0].object;
if (object !== cacheObject) {
if (cacheObject) {
cacheObject.dispatchEvent({
type: "mouseleave",
});
}
object.dispatchEvent({
type: "mouseenter",
});
} else if (object === cacheObject) {
object.dispatchEvent({
type: "mousemove",
});
}
cacheObject = object;
} else {
if (cacheObject) {
cacheObject.dispatchEvent({
type: "mouseleave",
});
}
cacheObject = null;
}
});
dom.addEventListener("mouseup", (event) => {
// 选取物体的操作
raycaster.setFromCamera(mouse, this.camera);
const intersection = raycaster.intersectObjects(this.scene.children);
this.dispatchEvent({
type: "mouseup",
intersection,
});
if (intersection.length) {
const object = intersection[0].object;
object.dispatchEvent({
type: "mouseup",
});
}
});
dom.addEventListener("click", (event) => {
// 选取物体的操作
raycaster.setFromCamera(mouse, this.camera);
let intersections = raycaster.intersectObjects(this.scene.children);
this.dispatchEvent({
type: "click",
intersections,
});
//移出变换控制器本身,并不想要其,会干扰
this.scene.remove(...this.base.noTransfromControlObjs);
intersections = raycaster.intersectObjects(this.scene.children);
//使用完要给添加回去
this.scene.add(...this.base.noTransfromControlObjs);
const targetIntersection = intersections.filter((intersection) => {
return (
// 除了上面的移出添加方法,也可是使用过滤的方法,来过滤掉一些不需要的内容
!(intersection.object instanceof TransformControlsPlane) &&
!(intersection.object instanceof AxesHelper) &&
!(intersection.object instanceof GridHelper) &&
!(intersection.object instanceof Line) &&
!(intersection.object.parent instanceof TransformControlsGizmo)
// !(intersection.object instanceof Line2) &&
);
});
if (targetIntersection.length) {
const object = targetIntersection[0].object;
console.log(object);
object.dispatchEvent({
type: "click",
});
}
});
}
}

threejs/Renderer.ts

import * as THREE from "three";
import Base from "./Base";
import { Scene, WebGLRenderer } from "three";
import Camera from "./Cameras";
import Size from "./Size";
import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer";
export default class Renderer {
private size: Size;
private scene: Scene;
private canvas: HTMLCanvasElement;
private camera: Camera;
private labelRenderer: CSS2DRenderer;
private renderer: WebGLRenderer;
constructor() {
const base = Base.getInstance();
this.size = base.size;
this.scene = base.scene;
this.canvas = base.canvas;
this.camera = base.cameras;
this.renderer = this.setRenderer();
const labelRenderer = this.setLabelRenderer();
this.labelRenderer = labelRenderer;
}
setRenderer() {
const renderer = new WebGLRenderer({
canvas: this.canvas,
antialias: true,
});
renderer.physicallyCorrectLights = true;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.CineonToneMapping;
renderer.toneMappingExposure = 1.75;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(this.size.width, this.size.height);
renderer.setPixelRatio(this.size.pixelRatio);
return renderer;
}
private setLabelRenderer() {
const labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(this.size.width, this.size.height);
document.body.appendChild(labelRenderer.domElement);
return labelRenderer;
}
resize() {
this.renderer.setSize(this.size.width, this.size.height);
this.renderer.setPixelRatio(this.size.pixelRatio);
// 重新渲染dom元素的画布
this.labelRenderer.setSize(this.size.width, this.size.height);
}
update() {
this.renderer.render(this.scene, this.camera.perspectiveCamera);
this.labelRenderer.render(this.scene, this.camera.perspectiveCamera);
}
}

threejs/Controls.ts

import {
TransformControls,
TransformControlsGizmo,
TransformControlsPlane,
} from "three/examples/jsm/controls/TransformControls";
import Base from "./Base";
import {
AxesHelper,
GridHelper,
Line,
MOUSE,
Object3D,
PerspectiveCamera,
Raycaster,
Scene,
Vector2,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
export default class Control {
public transformControl: TransformControls;
public orbitControl: OrbitControls;
private base: Base;
private camera: PerspectiveCamera;
private canvas: HTMLCanvasElement;
private scene: Scene;
private transing: boolean;
constructor() {
const base = Base.getInstance();
this.base = base;
this.camera = base.cameras.perspectiveCamera;
this.canvas = base.canvas;
this.scene = base.scene;
// 正在变换的标志位
this.transing = false;
this.transformControl = this.createTransformControl();
this.initTransformControlEvent();
this.scene.add(this.transformControl);
this.orbitControl = this.createOrbitControl();
}
private createTransformControl() {
const transformControls = new TransformControls(this.camera, this.canvas);
return transformControls;
}
private initTransformControlEvent() {
this.transformControl.addEventListener("mouseDown", (e) => {
this.transing = true;
});
}
public update() {
this.orbitControl.update();
}
private createOrbitControl() {
const controls = new OrbitControls(this.camera, this.canvas);
controls.enableDamping = true;
controls.enableZoom = true;
// 取消鼠标控制器的鼠标左键旋转功能
// controls.mouseButtons = {
// LEFT: null as unknown as MOUSE,
// MIDDLE: MOUSE.DOLLY,
// RIGHT: MOUSE.PAN,
// };
return controls;
}
}

threejs/Time.ts

import EventEmitter from "eventemitter2";
import { Clock } from "three";
export default class Time extends EventEmitter {
public clock: Clock;
constructor() {
super();
this.clock = new Clock();
this.update();
}
update() {
this.emit("update");
window.requestAnimationFrame(() => this.update());
}
}

threejs/World/Box.ts

import {
BoxGeometry,
MeshBasicMaterial,
Mesh,
MeshLambertMaterial,
Color,
} from "three";
import Base from "../Base";
export default class Box {
private cube: Mesh;
private cubeMaterial: MeshBasicMaterial;
constructor() {
const base = Base.getInstance();
this.cubeMaterial = new MeshBasicMaterial({ color: 0x00ff00 });
const cube = this.createCube();
this.cube = cube;
base.scene.add(cube);
}
private createCube() {
const cube = new Mesh(new BoxGeometry(1, 1, 1), this.cubeMaterial);
cube.position.y = 0.5;
return cube;
}
}
posted on   超级无敌美少男战士  阅读(707)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
历史上的今天:
2022-10-19 以三角形的形式遍历二维数组
点击右上角即可分享
微信分享提示