UNPKG

@matematrolii/sketchbook

Version:

3D matematrolii playground built on three.js and cannon.js

394 lines (342 loc) 10.3 kB
import * as THREE from "three"; // @ts-ignore import * as CANNON from "cannon"; import * as _ from "lodash"; import { SimulationFrame } from "../physics/spring_simulation/SimulationFrame"; import { World } from "../world/World"; import { Side } from "../enums/Side"; import { Object3D } from "three"; import { Space } from "../enums/Space"; import { ModelProps, ModelType } from "../enums/WorldType"; import { IWorldOptions } from "../interfaces/IWorldSettings"; const rootMaterial = new THREE.MeshPhongMaterial({ shininess: 0, skinning: true, }); export function createCapsuleGeometry( radius: number = 1, height: number = 2, N: number = 32 ): THREE.Geometry { const geometry = new THREE.Geometry(); const TWOPI = Math.PI * 2; const PID2 = 1.570796326794896619231322; const normals = []; // top cap for (let i = 0; i <= N / 4; i++) { for (let j = 0; j <= N; j++) { let theta = (j * TWOPI) / N; let phi = -PID2 + (Math.PI * i) / (N / 2); let vertex = new THREE.Vector3(); let normal = new THREE.Vector3(); vertex.x = radius * Math.cos(phi) * Math.cos(theta); vertex.y = radius * Math.cos(phi) * Math.sin(theta); vertex.z = radius * Math.sin(phi); vertex.z -= height / 2; normal.x = vertex.x; normal.y = vertex.y; normal.z = vertex.z; geometry.vertices.push(vertex); normals.push(normal); } } // bottom cap for (let i = N / 4; i <= N / 2; i++) { for (let j = 0; j <= N; j++) { let theta = (j * TWOPI) / N; let phi = -PID2 + (Math.PI * i) / (N / 2); let vertex = new THREE.Vector3(); let normal = new THREE.Vector3(); vertex.x = radius * Math.cos(phi) * Math.cos(theta); vertex.y = radius * Math.cos(phi) * Math.sin(theta); vertex.z = radius * Math.sin(phi); vertex.z += height / 2; normal.x = vertex.x; normal.y = vertex.y; normal.z = vertex.z; geometry.vertices.push(vertex); normals.push(normal); } } for (let i = 0; i <= N / 2; i++) { for (let j = 0; j < N; j++) { let vec = new THREE.Vector4( i * (N + 1) + j, i * (N + 1) + (j + 1), (i + 1) * (N + 1) + (j + 1), (i + 1) * (N + 1) + j ); if (i === N / 4) { let face1 = new THREE.Face3(vec.x, vec.y, vec.z, [ normals[vec.x], normals[vec.y], normals[vec.z], ]); let face2 = new THREE.Face3(vec.x, vec.z, vec.w, [ normals[vec.x], normals[vec.z], normals[vec.w], ]); geometry.faces.push(face2); geometry.faces.push(face1); } else { let face1 = new THREE.Face3(vec.x, vec.y, vec.z, [ normals[vec.x], normals[vec.y], normals[vec.z], ]); let face2 = new THREE.Face3(vec.x, vec.z, vec.w, [ normals[vec.x], normals[vec.z], normals[vec.w], ]); geometry.faces.push(face1); geometry.faces.push(face2); } } // if(i==(N/4)) break; // N/4 is when the center segments are solved } geometry.rotateX(Math.PI / 2); geometry.computeVertexNormals(); geometry.computeFaceNormals(); return geometry; } //#endregion //#region Math /** * Constructs a 2D matrix from first vector, replacing the Y axes with the global Y axis, * and applies this matrix to the second vector. Saves performance when compared to full 3D matrix application. * Useful for character rotation, as it only happens on the Y axis. * @param {Vector3} a Vector to construct 2D matrix from * @param {Vector3} b Vector to apply basis to */ export function appplyVectorMatrixXZ( a: THREE.Vector3, b: THREE.Vector3 ): THREE.Vector3 { return new THREE.Vector3(a.x * b.z + a.z * b.x, b.y, a.z * b.z + -a.x * b.x); } export function round(value: number, decimals: number = 0): number { return Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals); } export function roundVector( vector: THREE.Vector3, decimals: number = 0 ): THREE.Vector3 { return new THREE.Vector3( this.round(vector.x, decimals), this.round(vector.y, decimals), this.round(vector.z, decimals) ); } /** * Finds an angle between two vectors * @param {THREE.Vector3} v1 * @param {THREE.Vector3} v2 */ export function getAngleBetweenVectors( v1: THREE.Vector3, v2: THREE.Vector3, dotTreshold: number = 0.0005 ): number { let angle: number; let dot = v1.dot(v2); // If dot is close to 1, we'll round angle to zero if (dot > 1 - dotTreshold) { angle = 0; } else { // Dot too close to -1 if (dot < -1 + dotTreshold) { angle = Math.PI; } else { // Get angle difference in radians angle = Math.acos(dot); } } return angle; } /** * Finds an angle between two vectors with a sign relative to normal vector */ export function getSignedAngleBetweenVectors( v1: THREE.Vector3, v2: THREE.Vector3, normal: THREE.Vector3 = new THREE.Vector3(0, 1, 0), dotTreshold: number = 0.0005 ): number { let angle = this.getAngleBetweenVectors(v1, v2, dotTreshold); // Get vector pointing up or down let cross = new THREE.Vector3().crossVectors(v1, v2); // Compare cross with normal to find out direction if (normal.dot(cross) < 0) { angle = -angle; } return angle; } export function haveSameSigns(n1: number, n2: number): boolean { return n1 < 0 === n2 < 0; } export function haveDifferentSigns(n1: number, n2: number): boolean { return n1 < 0 !== n2 < 0; } //#endregion //#region Miscellaneous export function setDefaults(options: {}, defaults: {}): {} { return _.defaults({}, _.clone(options), defaults); } export function getGlobalProperties(prefix: string = ""): any[] { let keyValues = []; let global = window; // window for browser environments for (let prop in global) { // check the prefix if (prop.indexOf(prefix) === 0) { keyValues.push(prop /*+ "=" + global[prop]*/); } } return keyValues; // build the string } export function spring( source: number, dest: number, velocity: number, mass: number, damping: number ): SimulationFrame { let acceleration = dest - source; acceleration /= mass; velocity += acceleration; velocity *= damping; let position = source + velocity; return new SimulationFrame(position, velocity); } export function springV( source: THREE.Vector3, dest: THREE.Vector3, velocity: THREE.Vector3, mass: number, damping: number ): void { let acceleration = new THREE.Vector3().subVectors(dest, source); acceleration.divideScalar(mass); velocity.add(acceleration); velocity.multiplyScalar(damping); source.add(velocity); } export function threeVector(vec: CANNON.Vec3): THREE.Vector3 { return new THREE.Vector3(vec.x, vec.y, vec.z); } export function cannonVector(vec: THREE.Vector3): CANNON.Vec3 { return new CANNON.Vec3(vec.x, vec.y, vec.z); } export function threeQuat(quat: CANNON.Quaternion): THREE.Quaternion { return new THREE.Quaternion(quat.x, quat.y, quat.z, quat.w); } export function cannonQuat(quat: THREE.Quaternion): CANNON.Quaternion { return new CANNON.Quaternion(quat.x, quat.y, quat.z, quat.w); } export function setupMeshProperties(child: any, options: IWorldOptions): void { const isRoad = child[ModelProps.USER_DATA].name?.toLowerCase().indexOf('road') !== -1; child.castShadow = !isRoad; child.receiveShadow = true; if (child.material.name === ModelType.ROCKET) { const image = new Image(); image.crossOrigin = "Anonymous"; const texture = new THREE.Texture(image); image.onload = () => { texture.needsUpdate = true; }; image.src = options.rocketTexture; texture.flipY = false; const mat = rootMaterial.clone(); mat.map = texture; mat.name = child.material.name; mat.aoMap = child.material.aoMap; child.material = mat; } else if (child.material.map !== null) { const mat = rootMaterial.clone(); mat.name = child.material.name; mat.map = child.material.map; mat.aoMap = child.material.aoMap; mat.transparent = child.material.transparent; child.material = mat; } else { const mat = rootMaterial.clone(); mat.color = child.material.color; child.material = mat; } } export function detectRelativeSide(from: Object3D, to: Object3D): Side { const right = getRight(from, Space.Local); const viewVector = to.position.clone().sub(from.position).normalize(); return right.dot(viewVector) > 0 ? Side.Left : Side.Right; } export function easeInOutSine(x: number): number { return -(Math.cos(Math.PI * x) - 1) / 2; } export function easeOutQuad(x: number): number { return 1 - (1 - x) * (1 - x); } export function getRight( obj: THREE.Object3D, space: Space = Space.Global ): THREE.Vector3 { const matrix = getMatrix(obj, space); return new THREE.Vector3( matrix.elements[0], matrix.elements[1], matrix.elements[2] ); } export function getUp( obj: THREE.Object3D, space: Space = Space.Global ): THREE.Vector3 { const matrix = getMatrix(obj, space); return new THREE.Vector3( matrix.elements[4], matrix.elements[5], matrix.elements[6] ); } export function getForward( obj: THREE.Object3D, space: Space = Space.Global ): THREE.Vector3 { const matrix = getMatrix(obj, space); return new THREE.Vector3( matrix.elements[8], matrix.elements[9], matrix.elements[10] ); } export function getBack( obj: THREE.Object3D, space: Space = Space.Global ): THREE.Vector3 { const matrix = getMatrix(obj, space); return new THREE.Vector3( -matrix.elements[8], -matrix.elements[9], -matrix.elements[10] ); } export function getMatrix(obj: THREE.Object3D, space: Space): THREE.Matrix4 { switch (space) { case Space.Local: return obj.matrix; case Space.Global: return obj.matrixWorld; } } export function countSleepyBodies(): any { // let awake = 0; // let sleepy = 0; // let asleep = 0; // this.physicsWorld.bodies.forEach((body) => // { // if (body.sleepState === 0) awake++; // if (body.sleepState === 1) sleepy++; // if (body.sleepState === 2) asleep++; // }); } //#endregion