Commit 63e01312 authored by Master Q's avatar Master Q

oimo physicworld init

parents
node_modules/
dev
```javascript
yarn add webpack webpack-cli webpack-dev-server typescript three @types/three ts-loa
der
```
\ No newline at end of file
{
"name": "threexoimocarstudy-20220809",
"version": "1.0.0",
"description": "three and oimo for car",
"main": "index.ts",
"scripts": {
"dev": "webpack-dev-server",
"build": "webpack"
},
"keywords": [
"three",
"oimo"
],
"author": "lightfish",
"license": "ISC",
"dependencies": {
"@types/three": "^0.143.0",
"oimo": "^1.0.9",
"three": "^0.143.0",
"ts-loader": "^9.3.1",
"typescript": "^4.7.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three</title>
<style>
* {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<!-- 这里的 bundle 就是 webpack 临时打包出来 -->
<script src="bundle.js"></script>
</body>
</html>
\ No newline at end of file
import { EVENTS_ENUM, PerspectiveScene } from "../module/PerspectiveScene";
import * as THREE from 'three'
import { OimoPhysicWorld } from "../OimoPhysicWorld/OimoPhysicWorld";
function gradTexture(color: [number[], string[]]) {
var c = document.createElement("canvas");
var ct = c.getContext("2d");
c.width = 16; c.height = 256;
var gradient = ct.createLinearGradient(0,0,0,256);
var i = color[0].length;
while(i--){ gradient.addColorStop(color[0][i],color[1][i]); }
ct.fillStyle = gradient;
ct.fillRect(0,0,16,256);
var texture = new THREE.Texture(c);
texture.needsUpdate = true;
return texture;
}
function basicTexture(n: number){
var canvas = document.createElement( 'canvas' );
canvas.width = canvas.height = 64;
var ctx = canvas.getContext( '2d' );
var colors = [];
if(n===0){ // sphere
colors[0] = "#58AA80";
colors[1] = "#58FFAA";
}
if(n===1){ // sphere sleep
colors[0] = "#383838";
colors[1] = "#38AA80";
}
if(n===2){ // box
colors[0] = "#AA8058";
colors[1] = "#FFAA58";
}
if(n===3){ // box sleep
colors[0] = "#383838";
colors[1] = "#AA8038";
}
ctx.fillStyle = colors[0];
ctx.fillRect(0, 0, 64, 64);
ctx.fillStyle = colors[1];
ctx.fillRect(0, 0, 32, 32);
ctx.fillRect(32, 32, 32, 32);
var tx = new THREE.Texture(canvas);
tx.needsUpdate = true;
return tx;
}
const GeometryMap = {
'box': new THREE.BoxGeometry(1,1,1)
}
export class CarScene extends PerspectiveScene {
constructor() {
super()
this.initEvents()
this.initUi()
this.initPhysicWorld()
}
initUi() {
this.scene.background = new THREE.Color(0x000a6b)
const dirLight = new THREE.DirectionalLight(0xffffff)
dirLight.castShadow = true
dirLight.position.set(20, 20, 20)
dirLight.target.position.set(0, 0, 0)
this.scene.add(dirLight)
this.scene.add(new THREE.DirectionalLightHelper(dirLight))
let renderer = this.renderer
// 开启阴影
renderer.shadowMap.enabled = true
// THREE.BasicShadowMap-性能非常好但是质量很差
// THREE.PCFShadowMap-性能较差但边缘更平滑(默认)
// THREE.PCFSoftShadowMap-性能较差但边缘更柔和 radius 不会生效
// THREE.VSMShadowMap-性能差,约束多,但能够产生意想不到的效果。
renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.scene.add(new THREE.AmbientLight(0x3D4143))
this.scene.add(new THREE.AxesHelper(100))
this.scene.add(new THREE.GridHelper(100))
// let imgTexture = new THREE.TextureLoader().load('http://qnpic.top/yoona2.jpg')
// imgTexture.wrapS = imgTexture.wrapT = THREE.RepeatWrapping;
// imgTexture.encoding = THREE.sRGBEncoding;
// imgTexture.anisotropy = 16;
// imgTexture.repeat.set(1, 1);
// const plane = new THREE.Mesh(new THREE.PlaneGeometry(50, 50), new THREE.MeshLambertMaterial({
// map: imgTexture,
// side: THREE.DoubleSide,
// }))
// plane.rotateOnAxis(new THREE.Vector3(1, 0, 0), -Math.PI / 2)
// plane.position.y = -0.5
// plane.receiveShadow = true
// this.scene.add(plane)
const buffGeoBack = new THREE.IcosahedronGeometry(200, 1)
var back = new THREE.Mesh(buffGeoBack, new THREE.MeshBasicMaterial( { map:gradTexture([[1,0.75,0.5,0.25], ['#1B1D1E','#3D4143','#72797D', '#b0babf']]), side:THREE.BackSide, depthWrite: false } ));
// back.geometry.applyMatrix4(new THREE.Matrix4().makeRotationZ(15*ToRad));
this.scene.add( back );
}
physicWorld: OimoPhysicWorld
/**
* 创建物理世界
*/
initPhysicWorld() {
this.physicWorld = new OimoPhysicWorld()
console.log(this.physicWorld)
const boxGeometry = new THREE.BoxGeometry(1,1,1)
const material = new THREE.MeshLambertMaterial({
wireframe: true
})
material.color = new THREE.Color(0xffffff * Math.random())
const boxMesh = new THREE.Mesh(boxGeometry, material)
boxMesh.position.set(0, 20, 0)
this.scene.add(boxMesh)
const _ = this.physicWorld.addMesh(boxMesh, {
massPos: [1,1,1],
density: 1,
friction: 1,
// mass: 10000
})
const planeGeometry = new THREE.BoxGeometry(10, 2, 10)
const planeMesh = new THREE.Mesh(planeGeometry, new THREE.MeshPhongMaterial({
color: 0xffffff,
side: THREE.DoubleSide
}))
planeMesh.receiveShadow = true
// planeMesh.position.set(5, 0, 5)
// planeMesh.rotateX(Math.PI / 2)
this.scene.add(planeMesh)
const ridigBody = this.physicWorld.addMesh(planeMesh, {
move: false
})
// @ts-expect-error
window['test'] = function() {
_.applyImpulse(new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 0,0))
console.log(_)
}
}
initEvents() {
this.addEventListener(EVENTS_ENUM.ENTERFRAME, this.updateOimoPhysics, this)
this.addEventListener(EVENTS_ENUM.ENTERFRAME, this.onEnterFrame, this)
}
updateOimoPhysics() {
}
onEnterFrame() {
if (this.physicWorld) {
this.physicWorld.step()
}
}
}
\ No newline at end of file
import * as OIMO from 'oimo';
import * as THREE from 'three'
export class OimoPhysicWorld {
physicWorld: OIMO.World
meshes: THREE.Mesh[] = []
meshesMap: WeakMap<THREE.Mesh, OIMO.RigidBody> = new WeakMap()
constructor() {
this.initPhysicWorld()
}
initPhysicWorld() {
this.physicWorld = new OIMO.World({
timestep: 1 / 60,
gravity: [0,-9.8,0]
})
}
createMeshPhysicBody(mesh: THREE.Mesh, cfg: Partial<OIMO.BodyConfig> = {}) {
console.log(mesh.type, mesh, mesh.geometry)
// @ts-expect-error
const parameters = mesh.geometry.parameters;
const geometry = mesh.geometry;
if (geometry.type === 'BoxGeometry') {
const sx = parameters.width !== undefined ? parameters.width : 0.5;
const sy = parameters.height !== undefined ? parameters.height : 0.5;
const sz = parameters.depth !== undefined ? parameters.depth : 0.5;
return (this.physicWorld.add(Object.assign({
type: 'box',
pos: [mesh.position.x, mesh.position.y, mesh.position.z],
size: [sx, sy, sz],
move: true
}, cfg)));
}
// else if ( geometry.type === 'SphereGeometry' || geometry.type === 'IcosahedronGeometry' ) {
// const radius = parameters.radius !== undefined ? parameters.radius : 1;
// return new OIMO.OSphereGeometry( radius );
// }
return null;
}
addMesh(mesh: THREE.Mesh, config: Partial<OIMO.BodyConfig> = {}) {
const ridigBody = this.createMeshPhysicBody(mesh, config)
if (ridigBody) {
this.meshes.push(mesh)
this.meshesMap.set(mesh, ridigBody)
}
return ridigBody
}
step() {
this.physicWorld.step();
const meshes = this.meshes
for ( let i = 0, l = meshes.length; i < l; i ++ ) {
const mesh = meshes[i];
// if ( mesh.isInstancedMesh ) {
// const array = mesh.instanceMatrix.array;
// const bodies = meshMap.get( mesh );
// for ( let j = 0; j < bodies.length; j ++ ) {
// const body = bodies[ j ];
// compose( body.getPosition(), body.getOrientation(), array, j * 16 );
// }
// mesh.instanceMatrix.needsUpdate = true;
// } else
if ( mesh.isMesh ) {
const body = this.meshesMap.get(mesh);
mesh.position.copy(body.getPosition());
mesh.quaternion.copy(body.getQuaternion());
}
}
}
}
\ No newline at end of file
import { CarScene } from "./CarScene"
console.log('work')
new CarScene()
\ No newline at end of file
type EventCall<T extends any> = (e?: {
type: T,
data: any
}, ...arg1: any[]) => any
export class EventDispatcher<EventsKeyName extends string> {
private listeners: {
[key in EventsKeyName]?: {
listener: EventCall<EventsKeyName>,
context: any
}[]
} = {}
constructor() {
this.listeners = Object.create(null)
}
addEventListener(type: EventsKeyName, listener: EventCall<EventsKeyName>, context?: any) {
if (!this.listeners[type]) {
this.listeners[type] = []
}
this.listeners[type]!.push({
listener,
context
})
}
removeEventListener(type: EventsKeyName, listener: EventCall<EventsKeyName>) {
if (!this.listeners[type]) {
return
}
const cbs = this.listeners[type]!
let i = cbs.length - 1
while (i >= 0) {
const cb = cbs[i]
if (cb.listener === listener) {
cbs.splice(i, 1)
}
i --
}
// const index = this.listeners[type]!.findIndex(item => item.listener === listener)
// if (index > -1) {
// this.listeners[type]!.splice(index, 1)
// }
}
onceEventListener(type: EventsKeyName, listener: EventCall<EventsKeyName>, context?: any) {
const once = (...args: any[]) => {
listener.apply(context, args)
this.removeEventListener(type, once)
}
this.addEventListener(type, once, this)
}
dispatchEvent(type: EventsKeyName, args?: any) {
if (!this.listeners[type]) {
return
}
this.listeners[type]!.forEach(item => {
const {
listener,
context
} = item
listener.call(context, {
type,
data: args
})
})
}
}
\ No newline at end of file
This diff is collapsed.
import {
Clock,
PCFShadowMap,
PerspectiveCamera,
Scene,
WebGLRenderer
} from 'three'
import { EventDispatcher } from './EventDispatcher';
import { OrbitControls } from './OrbitControls/index';
export enum EVENTS_ENUM {
ENTERFRAME = 'ENTERFRAME',
}
// interface EventsCall {
// func: (e: EventsCallArgsType) => any,
// context: any
// }
// export interface EventsCallArgsType {
// type: EventsType,
// delta: number
// }
// type EventsMapInfer = {
// [x in EventsType]?: EventsCall[]
// }
export class PerspectiveScene extends EventDispatcher<EVENTS_ENUM> {
scene: Scene
camera: PerspectiveCamera
clock: Clock
renderer: WebGLRenderer
rendererDom: HTMLCanvasElement
OrbitControlsIns: OrbitControls
constructor() {
super()
this.clock = new Clock()
this.__initScene()
this.__initCamera()
this.__initRenderer()
this.__initEvents()
}
/**
* 初始化 场景
*/
private __initScene() {
const _scene = this.scene = new Scene()
}
private __initCamera() {
const _camera = this.camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
_camera.position.set(10, 10, 10)
_camera.lookAt(this.scene.position)
this.scene.add(_camera)
}
private __initRenderer() {
const matrialSceneCanvas = this.rendererDom = document.createElement('canvas')
matrialSceneCanvas.style.display = 'block'
const _renderer = this.renderer = new WebGLRenderer({
canvas: matrialSceneCanvas, // 如果指定了canvas,则不会创建一个新的canvas,
antialias: true, // 抗锯齿
})
// _renderer.shadowMap.enabled = true
// _renderer.shadowMap.type = PCFShadowMap
_renderer.setSize(window.innerWidth, window.innerHeight)
_renderer.setPixelRatio(window.devicePixelRatio)
document.body.appendChild(_renderer.domElement)
}
/**
* 初始化事件
*/
private __initEvents() {
this.frameLoop()
const OrbitControlsIns = this.OrbitControlsIns = new OrbitControls(this.camera, this.rendererDom)
window.addEventListener('resize', this.onWindowResize)
}
/**
* 清除事件
*/
private removeEvents() {
window.removeEventListener('resize', this.onWindowResize)
}
/**
* window resize
*/
private onWindowResize = () => {
this.camera.aspect = window.innerWidth / window.innerHeight
// 更新相机的投影矩阵
this.camera.updateProjectionMatrix()
this.renderer.setSize(window.innerWidth, window.innerHeight)
}
/**
* 帧循环
*/
private frameLoop() {
const delta = this.clock.getDelta()
this.dispatchEvent(EVENTS_ENUM.ENTERFRAME, {
delta,
})
// frame rander
this.renderer.render(this.scene, this.camera)
requestAnimationFrame(this.frameLoop.bind(this))
}
}
\ No newline at end of file
// import { Vector3 } from 'three';
// import { Vector3 } from "three"
declare module 'oimo' {
export interface BodyConfig {
type: string,
size: number[],
pos: number[],
move: boolean,
config: any[],
name: string,
mass: number,
massPos: number[],
density: number // 密度
friction: number // 摩擦力
}
export class RigidBody {
getPosition(): Vector3
getQuaternion(): Vector3
applyImpulse(position: Vector3, force: Vector3)
}
export class World {
constructor(config: {
info?: boolean
timestep: number,
gravity?: number[]
})
step()
add: (config: Partial<BodyConfig>) => RigidBody
initBody(type: string, o: BodyConfig)
}
}
\ No newline at end of file
{
"compilerOptions": {
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": [
"DOM",
"ES2015",
"ES5"
],
"sourceMap": true,
"noEmitOnError": true,
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": false,
"esModuleInterop": true,
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}
const path = require('path')
module.exports = {
mode: 'development',
devtool: 'source-map',
entry: {
index: './src/index.ts'
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /(node_modules)/,
use: [
{
loader: 'ts-loader',
}
],
exclude: /node_modules/
}
],
},
resolve: {
alias: {
'@': './src',
},
extensions: ['.ts', '.js', 'tsx']
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
devServer: {
port: '0.0.0.0',
hot: true,
open: true,
port: 9016
}
}
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment