webgl test and class validator mocker

This commit is contained in:
IDONTSUDO 2023-12-19 11:54:47 +03:00
parent b9a89a4ba7
commit 3ff2186deb
17 changed files with 368 additions and 72 deletions

View file

@ -29,7 +29,7 @@
"three": "^0.159.0",
"typescript": "^4.9.5",
"urdf-loader": "^0.12.1",
"uuid": "^9.0.0",
"uuid": "^9.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {

View file

@ -0,0 +1,42 @@
export interface Listener<T> {
(event: T): any;
}
export interface Disposable {
dispose(): void;
}
export class TypedEvent<T> {
private listeners: Listener<T>[] = [];
public listenersOnces: Listener<T>[] = [];
on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener);
return {
dispose: () => this.off(listener),
};
};
once = (listener: Listener<T>): void => {
this.listenersOnces.push(listener);
};
off = (listener: Listener<T>) => {
const callbackIndex = this.listeners.indexOf(listener);
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
};
emit = (event: T) => {
this.listeners.forEach((listener) => listener(event));
if (this.listenersOnces.length > 0) {
const toCall = this.listenersOnces;
this.listenersOnces = [];
toCall.forEach((listener) => listener(event));
}
};
pipe = (te: TypedEvent<T>): Disposable => {
return this.on((e) => te.emit(e));
};
}

View file

@ -12,15 +12,29 @@ import {
Object3DEventMap,
Box3,
Sphere,
LineBasicMaterial,
EdgesGeometry,
Raycaster,
LineSegments,
Vector2,
} from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { BaseSceneItemModel, StaticAssetItemModel } from "../../features/scene_manager/scene_manager_store";
import { TypedEvent } from "../helper/typed_event";
import { Result } from "../helper/result";
export class CoreThereRepository {
interface IEmissiveCache {
status: boolean;
object3d: Object3D<Object3DEventMap>;
}
export class CoreThereRepository extends TypedEvent<BaseSceneItemModel> {
scene = new Scene();
camera: PerspectiveCamera;
webGlRender: WebGLRenderer;
htmlCanvasRef: HTMLCanvasElement;
objectEmissive = new Map<string, IEmissiveCache>();
constructor(htmlCanvasRef: HTMLCanvasElement) {
super();
const renderer = new WebGLRenderer({
canvas: htmlCanvasRef as HTMLCanvasElement,
antialias: true,
@ -32,14 +46,22 @@ export class CoreThereRepository {
this.htmlCanvasRef = htmlCanvasRef;
this.init();
}
setRayCastAndGetFirstObject(vector: Vector2): Result<void, string> {
const raycaster = new Raycaster();
raycaster.setFromCamera(vector, this.camera);
const intersects = raycaster.intersectObjects(this.scene.children);
if (intersects.length > 0) {
return Result.ok(intersects[0].object.name);
}
return Result.error(undefined);
}
addCube(num: number, translateTo: string = "X") {
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshBasicMaterial({ color: 0x00ff00 });
const cube = new Mesh(geometry, material);
cube.name = "Cube" + String(num);
// cube.translateX(position.x);
// cube.translateY(position.y);
// cube.translateZ(position.z);
eval(`cube.translate${translateTo}(${num * 10})`);
this.scene.add(cube);
}
@ -70,6 +92,9 @@ export class CoreThereRepository {
this.webGlRender.render(this.scene, this.camera);
});
}
getAllSceneModels(): BaseSceneItemModel[] {
return this.getAllSceneNameModels().map((e) => new StaticAssetItemModel(e));
}
getAllSceneNameModels(): string[] {
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
}
@ -106,36 +131,55 @@ export class CoreThereRepository {
orbitControls.maxDistance = cameraToFarEdge * 2;
new OrbitControls(this.camera, this.htmlCanvasRef);
}
switchObjectEmissive(name: string) {
const mesh = this.getObjectsAtName(name);
const result = this.objectEmissive.get(mesh.name);
if (result?.status) {
this.scene.remove(mesh);
this.scene.add(result.object3d);
this.objectEmissive.set(mesh.name, {
status: false,
object3d: mesh,
});
} else {
this.objectEmissive.set(mesh.name, {
status: true,
object3d: mesh,
});
if (mesh instanceof Mesh) {
const newMesh = new LineSegments(mesh.geometry, new LineBasicMaterial({ color: 0x000000 }));
newMesh.name = mesh.name;
newMesh.translateX(mesh.position.x);
newMesh.translateY(mesh.position.y);
newMesh.translateZ(mesh.position.z);
this.scene.remove(mesh);
this.scene.add(newMesh);
}
}
}
fitSelectedObjectToScreen(objects: string[]) {
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
let boundBox = new Box3().setFromPoints(objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position));
let boundSphere = boundBox.getBoundingSphere(new Sphere());
let vFoV = this.camera.getEffectiveFOV();
let hFoV = this.camera.fov * this.camera.aspect;
let FoV = Math.min(vFoV, hFoV);
let FoV2 = FoV / 2;
let dir = new Vector3();
this.camera.getWorldDirection(dir);
let bsWorld = boundSphere.center.clone();
let th = (FoV2 * Math.PI) / 180.0;
let sina = Math.sin(th);
let R = boundSphere.radius;
let FL = R / sina;
let cameraDir = new Vector3();
let cameraOffs = cameraDir.clone();
console.log(cameraOffs);
cameraOffs.multiplyScalar(-FL);
console.log(-FL);
let newCameraPos = bsWorld.clone().add(cameraOffs);
console.log(newCameraPos);
this.camera.translateX(newCameraPos.x);
this.camera.translateY(newCameraPos.y);
this.camera.translateZ(newCameraPos.z);

View file

@ -0,0 +1,14 @@
import * as React from "react";
import { StaticAssetItemModel } from "../scene_manager_store";
export interface IStaticAssetModelViewProps {
model: StaticAssetItemModel;
}
export function StaticAssetModelView(props: IStaticAssetModelViewProps) {
return (
<div style={{ width: "100px", textAlignLast: "center", backgroundColor: "aqua", margin: "10px" }}>
{props.model.name}
</div>
);
}

View file

@ -1,43 +1,55 @@
import * as React from "react";
import { CoreThereRepository } from "../../core/repository/core_there_repository";
import { SceneMangerStore, StaticAssetItemModel } from "./scene_manager_store";
import { observer } from "mobx-react-lite";
import { StaticAssetModelView } from "./components/static_asset_item_view";
const useKeyLister = (fn: Function) => {
const pressed = new Map();
// const useKeyLister = (fn: Function) => {
// const pressed = new Map();
const registerKeyPress = React.useCallback(
(event: KeyboardEvent, codes: string[], callBack: Function) => {
if (codes.hasIncludeElement(event.code)) {
pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
callBack();
}
});
}
},
[pressed]
);
// const registerKeyPress = React.useCallback(
// (event: KeyboardEvent, codes: string[], callBack: Function) => {
// if (codes.hasIncludeElement(event.code)) {
// pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
// if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
// callBack();
// }
// });
// }
// },
// [pressed]
// );
React.useEffect(() => {
window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
}, [fn, registerKeyPress]);
// React.useEffect(() => {
// window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
// window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
// }, [fn, registerKeyPress]);
return [];
};
export function SceneManger() {
// return [];
// };
export const SceneManger = observer(() => {
const canvasRef = React.useRef<HTMLCanvasElement>(null);
let thereRepository: null | CoreThereRepository = null;
const [sceneMangerStore] = React.useState(() => new SceneMangerStore());
React.useEffect(() => {
// eslint-disable-next-line react-hooks/exhaustive-deps
thereRepository = new CoreThereRepository(canvasRef.current as HTMLCanvasElement);
thereRepository.render();
// thereRepository.fitSelectedObjectToScreen(thereRepository.getAllSceneNameModels());
thereRepository.fitCameraToCenteredObject(thereRepository.getAllSceneNameModels());
sceneMangerStore.loadGl(canvasRef.current!);
return () => {
sceneMangerStore.dispose();
};
});
return (
<div>
<div style={{ position: "absolute" }}>
{sceneMangerStore.sceneItems.map((el) => {
if (el instanceof StaticAssetItemModel) {
return StaticAssetModelView({ model: el });
}
return <></>;
})}
</div>
<canvas ref={canvasRef} />
</div>
);
}
});

View file

@ -0,0 +1,70 @@
/* eslint-disable array-callback-return */
import { makeAutoObservable } from "mobx";
import { CoreThereRepository } from "../../core/repository/core_there_repository";
import { v4 as uuidv4 } from "uuid";
import { Vector2 } from "three";
export class BaseSceneItemModel {
id: string;
constructor() {
this.id = uuidv4();
}
}
enum SceneModelsType {
ASSET,
}
export class StaticAssetItemModel extends BaseSceneItemModel {
name: string;
type = SceneModelsType.ASSET;
constructor(name: string) {
super();
this.name = name;
}
}
export class SceneMangerStore {
coreThereRepository: null | CoreThereRepository = null;
sceneItems: BaseSceneItemModel[] = [];
constructor() {
makeAutoObservable(this);
}
loadGl(canvasRef: HTMLCanvasElement): void {
this.coreThereRepository = new CoreThereRepository(canvasRef as HTMLCanvasElement);
this.coreThereRepository.on(this.watcherThereObjects);
this.coreThereRepository.render();
this.coreThereRepository.fitCameraToCenteredObject(this.coreThereRepository.getAllSceneNameModels());
this.sceneItems = this.coreThereRepository.getAllSceneModels();
window.addEventListener("click", this.handleMouseClick);
}
watcherThereObjects(sceneItemModel: BaseSceneItemModel): void {
this.sceneItems.push(sceneItemModel);
}
handleMouseClick = (event: MouseEvent) => {
const vector = new Vector2();
console.log("====");
console.log(event.pageX);
console.log(event.clientX);
console.log(event.x);
console.log(event.movementX);
console.log(event.screenX);
console.log("====");
vector.x = (event.clientX / window.innerWidth) * 2 - 1;
vector.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.coreThereRepository?.setRayCastAndGetFirstObject(vector).map((el) => {
this.coreThereRepository?.switchObjectEmissive(el);
});
};
dispose() {
window.removeEventListener("click", this.handleMouseClick);
}
}

View file

@ -14,8 +14,9 @@ const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
root.render(
<>
<SocketLister>
{/* <SocketLister>
<RouterProvider router={router} />
</SocketLister>
</SocketLister> */}
<SceneManger />
</>
);