/*----------------- ThreeSim.js------------------
an OOP simulator based on THREEJS
石家庄铁路职业技术学院 信息工程系 郑华 2024.11
tel:15227854875 , mail:7047052@qq.com
引用此模拟器,需在网页文件中引入以下代码:
<script type="importmap">
{
"imports": {
"three": "./js_three/build/three.module.min.js"
}
}
</script>
------------------------------------------------*/
import * as THREE from '../js_three/build/three.module.min.js';
import { Projector } from '../js_three/jsm/renderers/projector.js';
import { EffectComposer } from '../js_three/jsm/postprocessing/EffectComposer.js';
import { UnrealBloomPass } from '../js_three/jsm/postprocessing/UnrealBloomPass.js';
import { RenderPass } from '../js_three/jsm/postprocessing/RenderPass.js';
import { ThreeBase } from './ThreeBase.js';
//-----------------应用程序类-------------------
class ThreeApp extends ThreeBase {
static KeyCodes = {
KEY_LEFT : 37,
KEY_UP : 38,
KEY_RIGHT : 39,
KEY_DOWN : 40,
KEY_W : 87,
KEY_A : 65,
KEY_S : 83,
KEY_D : 68,
KEY_SPACE : 32
};
constructor() {
super(); // ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。
this.renderer = null; //只有调用super()之后,才可以使用this关键字,否则会报错。
this.scene = null;
this.camera = null;
this.objects = [];
}
init(param) {
param = param || {};
let container = param.container;
let canvas = param.canvas;
let renderer = new THREE.WebGLRenderer({ antialias: true, canvas: canvas, alpha: true });
renderer.setSize($(container).width(), $(container).height());
container.appendChild(renderer.domElement);
// Create a new Three.js scene
let scene = new THREE.Scene();
// 初始环境光
// scene.add( new THREE.AmbientLight( 0x101010 ) );
//这可用于通过ThreeObject对象反向获取ThreeApp对象
scene.data = this;
// Put in a camera at a good default location
let camera = new THREE.PerspectiveCamera(75, container.offsetWidth / container.offsetHeight, 0.1, 10000);
camera.position.set(0, 0, 0);
scene.add(camera);
// Create a root object to contain all other scene objects
let root = new THREE.Object3D();
scene.add(root);
// Create a projector to handle picking
let projector = new Projector();
// Save away a few things
this.container = container;
this.renderer = renderer;
this.scene = scene;
this.camera = camera;
this.projector = projector;
this.root = root;
// Set up event handlers
this.initMouse();
this.initKeyboard();
this.addDomHandlers();
}
initBloom() {
if (!this.container) {alert('应用程序没有初始化,请先执行init函数。');return;}
let that=this;
let width=$(this.container).width();
let height=$(this.container).height();
const params = {
exposure: 1,
bloomStrength: 1,
bloomThreshold: 0,
bloomRadius: 1,
scene: 'Scene with Glow'
};
let renderscene = new RenderPass(this.scene,this.camera);
let bloomPass = new UnrealBloomPass(new THREE.Vector2(width,height),1.5,0.4,0.85);
bloomPass.renderToscreen = true;
bloomPass.threshold = params.bloomThreshold;
bloomPass.strength = params.bloomStrength;
bloomPass.radius = params.bloomRadius;
this.bloomPass=bloomPass;
let composer = new EffectComposer(this.renderer);
composer.setSize(width,height);
composer.addPass(renderscene);
composer.addPass(bloomPass);
this.composer=composer;
}
run () {
this.update();
this.renderer.render( this.scene, this.camera );
let that = this;
requestAnimationFrame(function () { that.run(); });
}
runNorender() {
this.update();
let that = this;
requestAnimationFrame(function () { that.runNorender(); });
}
//必须配合initBloom()函数使用
runBloom() {
this.update();
let that = this;
this.camera.layers.set(1);
this.composer.render(); //先渲染bloom层
this.renderer.clearDepth(); //非常重要
this.camera.layers.set(0);
this.renderer.render(this.scene,this.camera);
requestAnimationFrame(function () { that.runBloom(); });
}
update(){
let i, len;
len = this.objects.length;
for (i = 0; i < len; i++)
{
this.objects[i].update();
}
}
addObject(obj){
this.objects.push(obj);
// If this is a renderable object, add it to the root scene
if (obj.object3D)
{
this.root.add(obj.object3D);
}
}
removeObject(obj){
let index = this.objects.indexOf(obj);
if (index != -1)
{
this.objects.splice(index, 1);
// If this is a renderable object, remove it from the root scene
if (obj.object3D)
{
this.root.remove(obj.object3D);
}
}
}
initMouse(){
let dom = this.renderer.domElement;
let that = this;
dom.addEventListener( 'mousemove',
function(e) { that.onDocumentMouseMove(e); }, false );
dom.addEventListener( 'mousedown',
function(e) { that.onDocumentMouseDown(e); }, false );
dom.addEventListener( 'mouseup',
function(e) { that.onDocumentMouseUp(e); }, false );
$(dom).mousewheel(
function(e, delta) {
that.onDocumentMouseScroll(e, delta);
}
);
this.overObject = null;
this.clickedObject = null;
}
initKeyboard(){
let dom = this.renderer.domElement;
let that = this;
dom.addEventListener( 'keydown',
function(e) { that.onKeyDown(e); }, false );
dom.addEventListener( 'keyup',
function(e) { that.onKeyUp(e); }, false );
dom.addEventListener( 'keypress',
function(e) { that.onKeyPress(e); }, false );
// so it can take focus
dom.setAttribute("tabindex", 1);
dom.style.outline='none';
}
addDomHandlers(){
let that = this;
window.addEventListener( 'resize', function(event) { that.onWindowResize(event); }, false );
}
onDocumentMouseMove(event){
event.preventDefault();
//单击某个对象了,并且有handleMouseMove事件
if (this.clickedObject && this.clickedObject.handleMouseMove)
{
let hitpoint = null, hitnormal = null;
let intersected = this.objectFromMouse(event.pageX, event.pageY);
if (intersected.object == this.clickedObject)
{
hitpoint = intersected.point;
hitnormal = intersected.normal;
}
this.clickedObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
}
else
{
let handled = false;
let oldObj = this.overObject;
let intersected = this.objectFromMouse(event.pageX, event.pageY);
this.overObject = intersected.object;
if (this.overObject != oldObj)
{
if (oldObj)
{
this.container.style.cursor = 'auto';
if (oldObj.handleMouseOut)
{
oldObj.handleMouseOut(event.pageX, event.pageY);
}
}
if (this.overObject)
{
if (this.overObject.overCursor)
{
this.container.style.cursor = this.overObject.overCursor;
}
if (this.overObject.handleMouseOver)
{
this.overObject.handleMouseOver(event.pageX, event.pageY);
}
}
handled = true;
}
if (this.overObject)
{
let hitpoint = intersected.point;
let hitnormal = intersected.normal;
this.overObject.handleMouseMove(event.pageX, event.pageY, hitpoint, hitnormal);
}
if (!handled && this.handleMouseMove) //处理app类的handleMouseMove事件
{
this.handleMouseMove(event.pageX, event.pageY,event);
}
}
}
onDocumentMouseDown(event) {
event.preventDefault();
let handled = false;
let intersected = this.objectFromMouse(event.pageX, event.pageY);
if (intersected.object) {
if (intersected.object.handleMouseDown) {
intersected.object.handleMouseDown(event.pageX, event.pageY, intersected.point, intersected.normal, event);
this.clickedObject = intersected.object;
}
}
if (!handled && this.handleMouseDown) {
this.handleMouseDown(event.pageX, event.pageY, event);
}
}
onDocumentMouseUp(event){
event.preventDefault();
let handled = false;
let intersected = this.objectFromMouse(event.pageX, event.pageY);
if (intersected.object)
{
if (intersected.object.handleMouseUp)
{
intersected.object.handleMouseUp(event.pageX, event.pageY, intersected.point, intersected.normal, event);
}
}
if (!handled && this.handleMouseUp)
{
this.handleMouseUp(event.pageX, event.pageY, event);
}
this.clickedObject = null;
}
onDocumentMouseScroll(event, delta){
event.preventDefault();
if (this.handleMouseScroll)
{
this.handleMouseScroll(delta);
}
}
objectFromMouse(pagex, pagey) {
// Translate page coords to element coords
let offset = $(this.renderer.domElement).offset();
let eltx = pagex - offset.left;
let elty = pagey - offset.top;
// Translate client coords into viewport x,y
let vpx = (eltx / this.container.offsetWidth) * 2 - 1;
let vpy = -(elty / this.container.offsetHeight) * 2 + 1;
let vector = new THREE.Vector3(vpx, vpy, 0.5);
//this.projector.unprojectVector(vector, this.camera); //已淘汰
vector.unproject( this.camera ); //zh.h,2022.10.2
//var ray = new THREE.Ray( this.camera.position, vector.subSelf( this.camera.position ).normalize() );
let ray = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize());
//当对象处于其他层(默认为0层)时,也能被鼠标选中(按需修改),zhh,2024.4.29
ray.layers.enableAll();
//var intersects = ray.intersectScene( this.scene );
let objs = [];
for (let i = 0; i < this.objects.length; i++) {
if (!this.objects[i].object3D.type == "Mesh") continue; //2024.5.4
objs.push(this.objects[i].object3D); //by zh.h,2014.9.12(郑华)
this.objects[i].object3D.traverse(function (obj) {
if (obj.type == "Mesh") { //2024.5.4
objs.push(obj);
}
});
}
let intersects = ray.intersectObjects(objs);
//alert(intersects.length);
if (intersects.length > 0) {
let i = 0;
while (!intersects[i].object.visible) {
i++;
}
let intersected = intersects[i];
//var mat = new THREE.Matrix4().getInverse(intersected.object.matrixWorld); //已淘汰
let mat = new THREE.Matrix4().copy( intersected.object.matrixWorld).invert();
//zh.h,2022.10.2
//var point = mat.multiplyVector3(intersected.point);//已淘汰
let point = intersected.point.applyMatrix4( mat );
//zh.h,2022.10.2
return (this.findObjectFromIntersected(intersected.object, intersected.point, intersected.face.normal));
}
else {
return { object: null, point: null, normal: null };
}
}
findObjectFromIntersected(object, point, normal){
if (object.data)
{
return { object: object.data, point: point, normal: normal };
}
else if (object.parent)
{
return this.findObjectFromIntersected(object.parent, point, normal);
}
else
{
return { object : null, point : null, normal : null };
}
}
onKeyDown(event){
// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
event.preventDefault();
if (this.handleKeyDown)
{
this.handleKeyDown(event);
}
}
onKeyUp(event){
// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
event.preventDefault();
if (this.handleKeyUp)
{
this.handleKeyUp(event);
}
}
onKeyPress(event){
// N.B.: Chrome doesn't deliver keyPress if we don't bubble... keep an eye on this
event.preventDefault();
if (this.handleKeyPress)
{
this.handleKeyPress(event);
}
}
onWindowResize(event) {
this.renderer.setSize(this.container.offsetWidth, this.container.offsetHeight);
this.camera.aspect = this.container.offsetWidth / this.container.offsetHeight;
this.camera.updateProjectionMatrix();
}
focus() {
if (this.renderer && this.renderer.domElement)
{
this.renderer.domElement.focus();
}
}
}
//-----------------3D对象类-------------------
class ThreeObject extends ThreeBase {
constructor() {
super(); // ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。
this.object3D = null;
this.children = [];
this.visible = true; //zh.h,2022.5.5
}
init(){
}
update(){
let i, len;
len = this.children.length;
for (i = 0; i < len; i++)
{
this.children[i].update();
}
}
setPosition(x, y, z){
if (this.object3D)
{
this.object3D.position.set(x, y, z);
}
}
setRotation(x, y, z){
if (this.object3D)
{
this.object3D.rotation.set(x, y, z);
}
}
setScale(x, y, z){
if (this.object3D)
{
this.object3D.scale.set(x, y, z);
}
}
setVisible(visible){
function setVisible(obj, visible)
{
obj.visible = visible;
let i, len = obj.children.length;
for (i = 0; i < len; i++)
{
setVisible(obj.children[i], visible);
}
}
if (this.object3D)
{
setVisible(this.object3D, visible);
}
this.visible=visible;
}
setLayers(layer){
function setLayers(obj, layer)
{
obj.layers.set(layer);
let i, len = obj.children.length;
for (i = 0; i < len; i++)
{
setLayers(obj.children[i], layer);
}
}
if (this.object3D)
{
setLayers(this.object3D, layer);
}
}
setObject3D (object3D){
//这可用于反向取得ThreeApp对象
object3D.data = this;
this.object3D = object3D;
}
addChild(child){
this.children.push(child);
// If this is a renderable object, add its object3D as a child of mine
if (child.object3D)
{
this.object3D.add(child.object3D);
}
}
removeChild(child){
var index = this.children.indexOf(child);
if (index != -1)
{
this.children.splice(index, 1);
// If this is a renderable object, remove its object3D as a child of mine
if (child.object3D)
{
this.object3D.remove(child.object3D);
}
}
}
getScene(){
let scene = null;
if (this.object3D)
{
let obj = this.object3D;
while (obj.parent)
{
obj = obj.parent;
}
scene = obj;
}
return scene;
}
getApp(){
let scene = this.getScene();
return scene ? scene.data : null;
}
//待用户自定义,可用于回调函数
userAction(){
}
}
export {ThreeBase,ThreeApp,ThreeObject};
该类支持以面向对象的方式进行threejs编程,在事件处理、对象管理等方面带来很多便利。