webgl test and class validator mocker
This commit is contained in:
parent
b9a89a4ba7
commit
3ff2186deb
17 changed files with 368 additions and 72 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -15,6 +15,6 @@
|
||||||
"*ui": false,
|
"*ui": false,
|
||||||
"*ui.*": false
|
"*ui.*": false
|
||||||
},
|
},
|
||||||
"cSpell.words": ["antd", "fileupload", "uuidv"],
|
"cSpell.words": ["antd", "fileupload", "metadatas", "uuidv"],
|
||||||
"editor.rulers": [100]
|
"editor.rulers": [100]
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,9 @@ export class App {
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAppDependencies() {
|
async loadAppDependencies() {
|
||||||
await new DataBaseConnectUseCase().call();
|
if ((await new DataBaseConnectUseCase().call()).isFailure()) {
|
||||||
|
console.log("database connect error");
|
||||||
|
}
|
||||||
await new CheckAndCreateStaticFilesFolderUseCase().call();
|
await new CheckAndCreateStaticFilesFolderUseCase().call();
|
||||||
await new SetLastActivePipelineToRealTimeServiceScenario().call();
|
await new SetLastActivePipelineToRealTimeServiceScenario().call();
|
||||||
}
|
}
|
||||||
|
|
82
server/src/core/helpers/class_validator_mocket.ts
Normal file
82
server/src/core/helpers/class_validator_mocket.ts
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import { randomBytes, randomInt, randomUUID } from "crypto";
|
||||||
|
import { getMetadataStorage, IS_BOOLEAN, IS_MONGO_ID, IS_NUMBER, IS_STRING, IS_UUID } from "class-validator";
|
||||||
|
import { ValidationMetadata } from "class-validator/types/metadata/ValidationMetadata";
|
||||||
|
|
||||||
|
type AvailableTypes = string | number | boolean | undefined;
|
||||||
|
|
||||||
|
export class ClassValidatorMocker {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
public static create<T>(constructor: Function, partial: Partial<T> = {}): T {
|
||||||
|
return new ClassValidatorMocker().create(constructor, partial);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
public create<T>(constructor: Function, partial: Partial<T> = {}): T {
|
||||||
|
const metadataStorage = getMetadataStorage();
|
||||||
|
const targetMetadatas = metadataStorage.getTargetValidationMetadatas(constructor, "", false, false);
|
||||||
|
const groupedMetadatas = metadataStorage.groupByPropertyName(targetMetadatas);
|
||||||
|
// nestedValidation
|
||||||
|
console.log(targetMetadatas);
|
||||||
|
let randomFixture = {} as T;
|
||||||
|
|
||||||
|
for (const propertyName of Object.keys(groupedMetadatas)) {
|
||||||
|
const metadatas = groupedMetadatas[propertyName];
|
||||||
|
const value = this.generatePropertyValueFromMetadatas(metadatas);
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
randomFixture = {
|
||||||
|
...randomFixture,
|
||||||
|
[propertyName]: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...randomFixture, ...partial };
|
||||||
|
}
|
||||||
|
|
||||||
|
private generatePropertyValueFromMetadatas(metadatas: ValidationMetadata[]): AvailableTypes {
|
||||||
|
for (const metadata of metadatas) {
|
||||||
|
const constraints = getMetadataStorage().getTargetValidatorConstraints(metadata.constraintCls);
|
||||||
|
|
||||||
|
for (const constraint of constraints) {
|
||||||
|
switch (constraint.name) {
|
||||||
|
case IS_MONGO_ID:
|
||||||
|
return this.randomUUID();
|
||||||
|
case IS_STRING:
|
||||||
|
return this.randomString();
|
||||||
|
case IS_NUMBER:
|
||||||
|
return this.randomNumber();
|
||||||
|
case IS_BOOLEAN:
|
||||||
|
return this.randomBoolean();
|
||||||
|
case IS_UUID:
|
||||||
|
return this.randomUUID();
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomString(): string {
|
||||||
|
return randomBytes(randomInt(1, 10)).toString("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomNumber(): number {
|
||||||
|
return randomInt(0, 99_999);
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomBoolean(): boolean {
|
||||||
|
return randomInt(0, 1) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomUUID(): string {
|
||||||
|
if (randomUUID != null) {
|
||||||
|
return randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomBytes(16).toString("hex");
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { IsOptional, ValidateNested } from "class-validator";
|
||||||
import { IPipeline, IProcess, StackGenerateType } from "../../../core/models/process_model";
|
import { IPipeline, IProcess, StackGenerateType } from "../../../core/models/process_model";
|
||||||
import { Type } from "class-transformer";
|
import { Type } from "class-transformer";
|
||||||
import { ProcessModel } from "../../process/models/process_validation_model";
|
import { ProcessModel } from "../../process/models/process_validation_model";
|
||||||
import { TriggerModel } from "../../triggers/models/trigger_validation_model";
|
import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model";
|
||||||
|
|
||||||
export class PipelineModel implements IPipeline {
|
export class PipelineModel implements IPipeline {
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
|
@ -10,8 +10,8 @@ export class PipelineModel implements IPipeline {
|
||||||
public process: IProcess;
|
public process: IProcess;
|
||||||
|
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => TriggerModel)
|
@Type(() => TriggerModelValidationModel)
|
||||||
public trigger: TriggerModel;
|
public trigger: TriggerModelValidationModel;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
public env = null;
|
public env = null;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { IsMongoId, IsOptional } from "class-validator";
|
import { IsMongoId, IsOptional } from "class-validator";
|
||||||
import { IProcess, StackGenerateType } from "../../../core/models/process_model";
|
import { IProcess, StackGenerateType } from "../../../core/models/process_model";
|
||||||
import { TriggerModel } from "../../triggers/models/trigger_validation_model";
|
import { TriggerModelValidationModel } from "../../triggers/models/trigger_validation_model";
|
||||||
|
|
||||||
export class PipelineValidationModel {
|
export class PipelineValidationModel {
|
||||||
@IsMongoId()
|
@IsMongoId()
|
||||||
public process: IProcess;
|
public process: IProcess;
|
||||||
|
|
||||||
@IsMongoId()
|
@IsMongoId()
|
||||||
public trigger: TriggerModel;
|
public trigger: TriggerModelValidationModel;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
public env = null;
|
public env = null;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { IsArray, IsOptional, IsEnum, IsString } from "class-validator";
|
import { IsArray, IsOptional, IsEnum, IsString } from "class-validator";
|
||||||
import { ITriggerModel, TriggerType } from "./trigger_database_model";
|
import { ITriggerModel, TriggerType } from "./trigger_database_model";
|
||||||
|
|
||||||
export class TriggerModel implements ITriggerModel {
|
export class TriggerModelValidationModel implements ITriggerModel {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
public _id: string;
|
public _id: string;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { CrudController } from "../../core/controllers/crud_controller";
|
import { CrudController } from "../../core/controllers/crud_controller";
|
||||||
import { TriggerDBModel } from "./models/trigger_database_model";
|
import { TriggerDBModel } from "./models/trigger_database_model";
|
||||||
import { TriggerModel } from "./models/trigger_validation_model";
|
import { TriggerModelValidationModel as TriggerValidationMode } from "./models/trigger_validation_model";
|
||||||
|
|
||||||
export class TriggerPresentation extends CrudController<TriggerModel, typeof TriggerDBModel> {
|
export class TriggerPresentation extends CrudController<TriggerValidationMode, typeof TriggerDBModel> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
url: "trigger",
|
url: "trigger",
|
||||||
validationModel: TriggerModel,
|
validationModel: TriggerValidationMode,
|
||||||
databaseModel: TriggerDBModel,
|
databaseModel: TriggerDBModel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ import { ProcessPresentation } from "./features/process/process_presentation";
|
||||||
import { RealTimePresentation, pipelineRealTimeService } from "./features/realtime/realtime_presentation";
|
import { RealTimePresentation, pipelineRealTimeService } from "./features/realtime/realtime_presentation";
|
||||||
import { extensions } from "./core/extensions/extensions";
|
import { extensions } from "./core/extensions/extensions";
|
||||||
import { ProjectInstancePresentation } from "./features/project_instance/project_instance_presentation";
|
import { ProjectInstancePresentation } from "./features/project_instance/project_instance_presentation";
|
||||||
import { NixStoreManagerPresentation as NixStoreManagerPresentation } from "./features/nix_store_manager/nix_store_manager";
|
import { NixStoreManagerPresentation } from "./features/nix_store_manager/nix_store_manager";
|
||||||
|
|
||||||
extensions();
|
extensions();
|
||||||
|
|
||||||
const httpRoutes: Routes[] = [
|
export const httpRoutes: Routes[] = [
|
||||||
new TriggerPresentation(),
|
new TriggerPresentation(),
|
||||||
new ProjectsPresentation(),
|
new ProjectsPresentation(),
|
||||||
new ProcessPresentation(),
|
new ProcessPresentation(),
|
||||||
|
|
25
server/test/helper/class_validator_mocker_test.ts
Normal file
25
server/test/helper/class_validator_mocker_test.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { Type } from "class-transformer";
|
||||||
|
import { ClassValidatorMocker } from "../../src/core/helpers/class_validator_mocket";
|
||||||
|
import { IsString, IsNumber, IsBoolean, IsUUID, IsMongoId, ValidateNested } from "class-validator";
|
||||||
|
|
||||||
|
class Foo {}
|
||||||
|
class MyClass {
|
||||||
|
@ValidateNested()
|
||||||
|
@Type(() => Foo)
|
||||||
|
model: Foo;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
numberProperty: number;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
booleanProperty: boolean;
|
||||||
|
|
||||||
|
@IsUUID()
|
||||||
|
uuidProperty: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const myClassDataMock = ClassValidatorMocker.create<MyClass>(MyClass);
|
||||||
|
|
||||||
|
export const mainTest = () => {
|
||||||
|
console.log(myClassDataMock);
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
import "reflect-metadata";
|
||||||
import { TestCore } from "./core/test_core";
|
import { TestCore } from "./core/test_core";
|
||||||
// import { UnitTestEnv } from "../src/core/di/env";
|
// import { UnitTestEnv } from "../src/core/di/env";
|
||||||
import { dirname } from "path";
|
import { dirname } from "path";
|
||||||
|
@ -13,6 +14,7 @@ import { UpdateDataBaseModelUseCaseTest } from "./usecases/update_database_model
|
||||||
import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test";
|
import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test";
|
||||||
import { extensions } from "../src/core/extensions/extensions";
|
import { extensions } from "../src/core/extensions/extensions";
|
||||||
import { DataBaseConnectUseCase } from "../src/core/usecases/database_connect_usecase";
|
import { DataBaseConnectUseCase } from "../src/core/usecases/database_connect_usecase";
|
||||||
|
import { mainTest } from "./helper/class_validator_mocker_test";
|
||||||
|
|
||||||
extensions();
|
extensions();
|
||||||
|
|
||||||
|
@ -34,20 +36,22 @@ const init = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const test = async () => {
|
const test = async () => {
|
||||||
await new ExecutorProgramServiceTest(dirname__).test();
|
// await new ExecutorProgramServiceTest(dirname__).test();
|
||||||
await new FilesChangerTest(dirname__).test();
|
// await new FilesChangerTest(dirname__).test();
|
||||||
await new StackServiceTest(dirname__ + "/context/").test();
|
// await new StackServiceTest(dirname__ + "/context/").test();
|
||||||
await new TriggerServiceTest().test();
|
// await new TriggerServiceTest().test();
|
||||||
await new CreateDataBaseModelUseCaseTest().test();
|
// await new CreateDataBaseModelUseCaseTest().test();
|
||||||
|
|
||||||
await new CreateDataBaseModelUseCaseTest().test();
|
// await new CreateDataBaseModelUseCaseTest().test();
|
||||||
await new DeleteDataBaseModelUseCaseTest().test();
|
// await new DeleteDataBaseModelUseCaseTest().test();
|
||||||
await new ReadDataBaseModelUseCaseTest().test();
|
// await new ReadDataBaseModelUseCaseTest().test();
|
||||||
await new UpdateDataBaseModelUseCaseTest().test();
|
// await new UpdateDataBaseModelUseCaseTest().test();
|
||||||
// await new PipelineRealTimeServiceTest().test()
|
// // await new PipelineRealTimeServiceTest().test()
|
||||||
for await (const usecase of tests) {
|
// for await (const usecase of tests) {
|
||||||
testCore.assert(await new usecase().test(), usecase.name);
|
// testCore.assert(await new usecase().test(), usecase.name);
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
mainTest();
|
||||||
};
|
};
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
await init();
|
await init();
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"three": "^0.159.0",
|
"three": "^0.159.0",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"urdf-loader": "^0.12.1",
|
"urdf-loader": "^0.12.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
42
ui/src/core/helper/typed_event.ts
Normal file
42
ui/src/core/helper/typed_event.ts
Normal 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));
|
||||||
|
};
|
||||||
|
}
|
|
@ -12,15 +12,29 @@ import {
|
||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
Box3,
|
Box3,
|
||||||
Sphere,
|
Sphere,
|
||||||
|
LineBasicMaterial,
|
||||||
|
EdgesGeometry,
|
||||||
|
Raycaster,
|
||||||
|
LineSegments,
|
||||||
|
Vector2,
|
||||||
} from "three";
|
} from "three";
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
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();
|
scene = new Scene();
|
||||||
camera: PerspectiveCamera;
|
camera: PerspectiveCamera;
|
||||||
webGlRender: WebGLRenderer;
|
webGlRender: WebGLRenderer;
|
||||||
htmlCanvasRef: HTMLCanvasElement;
|
htmlCanvasRef: HTMLCanvasElement;
|
||||||
|
objectEmissive = new Map<string, IEmissiveCache>();
|
||||||
constructor(htmlCanvasRef: HTMLCanvasElement) {
|
constructor(htmlCanvasRef: HTMLCanvasElement) {
|
||||||
|
super();
|
||||||
const renderer = new WebGLRenderer({
|
const renderer = new WebGLRenderer({
|
||||||
canvas: htmlCanvasRef as HTMLCanvasElement,
|
canvas: htmlCanvasRef as HTMLCanvasElement,
|
||||||
antialias: true,
|
antialias: true,
|
||||||
|
@ -32,14 +46,22 @@ export class CoreThereRepository {
|
||||||
this.htmlCanvasRef = htmlCanvasRef;
|
this.htmlCanvasRef = htmlCanvasRef;
|
||||||
this.init();
|
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") {
|
addCube(num: number, translateTo: string = "X") {
|
||||||
const geometry = new BoxGeometry(1, 1, 1);
|
const geometry = new BoxGeometry(1, 1, 1);
|
||||||
const material = new MeshBasicMaterial({ color: 0x00ff00 });
|
const material = new MeshBasicMaterial({ color: 0x00ff00 });
|
||||||
const cube = new Mesh(geometry, material);
|
const cube = new Mesh(geometry, material);
|
||||||
cube.name = "Cube" + String(num);
|
cube.name = "Cube" + String(num);
|
||||||
// cube.translateX(position.x);
|
|
||||||
// cube.translateY(position.y);
|
|
||||||
// cube.translateZ(position.z);
|
|
||||||
eval(`cube.translate${translateTo}(${num * 10})`);
|
eval(`cube.translate${translateTo}(${num * 10})`);
|
||||||
this.scene.add(cube);
|
this.scene.add(cube);
|
||||||
}
|
}
|
||||||
|
@ -70,6 +92,9 @@ export class CoreThereRepository {
|
||||||
this.webGlRender.render(this.scene, this.camera);
|
this.webGlRender.render(this.scene, this.camera);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
getAllSceneModels(): BaseSceneItemModel[] {
|
||||||
|
return this.getAllSceneNameModels().map((e) => new StaticAssetItemModel(e));
|
||||||
|
}
|
||||||
getAllSceneNameModels(): string[] {
|
getAllSceneNameModels(): string[] {
|
||||||
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
|
return this.scene.children.filter((el) => el.name !== "").map((el) => el.name);
|
||||||
}
|
}
|
||||||
|
@ -106,36 +131,55 @@ export class CoreThereRepository {
|
||||||
orbitControls.maxDistance = cameraToFarEdge * 2;
|
orbitControls.maxDistance = cameraToFarEdge * 2;
|
||||||
new OrbitControls(this.camera, this.htmlCanvasRef);
|
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[]) {
|
fitSelectedObjectToScreen(objects: string[]) {
|
||||||
//https://stackoverflow.com/questions/14614252/how-to-fit-camera-to-object
|
//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 boundBox = new Box3().setFromPoints(objects.map((el) => this.getObjectsAtName(el)).map((el) => el.position));
|
||||||
let boundSphere = boundBox.getBoundingSphere(new Sphere());
|
let boundSphere = boundBox.getBoundingSphere(new Sphere());
|
||||||
let vFoV = this.camera.getEffectiveFOV();
|
let vFoV = this.camera.getEffectiveFOV();
|
||||||
let hFoV = this.camera.fov * this.camera.aspect;
|
let hFoV = this.camera.fov * this.camera.aspect;
|
||||||
|
|
||||||
let FoV = Math.min(vFoV, hFoV);
|
let FoV = Math.min(vFoV, hFoV);
|
||||||
let FoV2 = FoV / 2;
|
let FoV2 = FoV / 2;
|
||||||
|
|
||||||
let dir = new Vector3();
|
let dir = new Vector3();
|
||||||
this.camera.getWorldDirection(dir);
|
this.camera.getWorldDirection(dir);
|
||||||
|
|
||||||
let bsWorld = boundSphere.center.clone();
|
let bsWorld = boundSphere.center.clone();
|
||||||
|
|
||||||
let th = (FoV2 * Math.PI) / 180.0;
|
let th = (FoV2 * Math.PI) / 180.0;
|
||||||
let sina = Math.sin(th);
|
let sina = Math.sin(th);
|
||||||
let R = boundSphere.radius;
|
let R = boundSphere.radius;
|
||||||
let FL = R / sina;
|
let FL = R / sina;
|
||||||
|
|
||||||
let cameraDir = new Vector3();
|
let cameraDir = new Vector3();
|
||||||
|
|
||||||
let cameraOffs = cameraDir.clone();
|
let cameraOffs = cameraDir.clone();
|
||||||
console.log(cameraOffs);
|
|
||||||
cameraOffs.multiplyScalar(-FL);
|
cameraOffs.multiplyScalar(-FL);
|
||||||
console.log(-FL);
|
|
||||||
let newCameraPos = bsWorld.clone().add(cameraOffs);
|
let newCameraPos = bsWorld.clone().add(cameraOffs);
|
||||||
console.log(newCameraPos);
|
|
||||||
this.camera.translateX(newCameraPos.x);
|
this.camera.translateX(newCameraPos.x);
|
||||||
this.camera.translateY(newCameraPos.y);
|
this.camera.translateY(newCameraPos.y);
|
||||||
this.camera.translateZ(newCameraPos.z);
|
this.camera.translateZ(newCameraPos.z);
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,43 +1,55 @@
|
||||||
import * as React from "react";
|
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 useKeyLister = (fn: Function) => {
|
||||||
const pressed = new Map();
|
// const pressed = new Map();
|
||||||
|
|
||||||
const registerKeyPress = React.useCallback(
|
// const registerKeyPress = React.useCallback(
|
||||||
(event: KeyboardEvent, codes: string[], callBack: Function) => {
|
// (event: KeyboardEvent, codes: string[], callBack: Function) => {
|
||||||
if (codes.hasIncludeElement(event.code)) {
|
// if (codes.hasIncludeElement(event.code)) {
|
||||||
pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
|
// pressed.addValueOrMakeCallback(event.code, event.type, (e) => {
|
||||||
if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
|
// if (Array.from(pressed.values()).equals(["keydown", "keydown"], false)) {
|
||||||
callBack();
|
// callBack();
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
[pressed]
|
// [pressed]
|
||||||
);
|
// );
|
||||||
|
|
||||||
React.useEffect(() => {
|
// React.useEffect(() => {
|
||||||
window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
|
// window.addEventListener("keyup", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => fn));
|
||||||
window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
|
// window.addEventListener("keydown", (e) => registerKeyPress(e, ["KeyQ", "KeyW"], () => {}));
|
||||||
}, [fn, registerKeyPress]);
|
// }, [fn, registerKeyPress]);
|
||||||
|
|
||||||
return [];
|
// return [];
|
||||||
};
|
// };
|
||||||
export function SceneManger() {
|
|
||||||
|
export const SceneManger = observer(() => {
|
||||||
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
const canvasRef = React.useRef<HTMLCanvasElement>(null);
|
||||||
let thereRepository: null | CoreThereRepository = null;
|
|
||||||
|
const [sceneMangerStore] = React.useState(() => new SceneMangerStore());
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
sceneMangerStore.loadGl(canvasRef.current!);
|
||||||
thereRepository = new CoreThereRepository(canvasRef.current as HTMLCanvasElement);
|
return () => {
|
||||||
thereRepository.render();
|
sceneMangerStore.dispose();
|
||||||
// thereRepository.fitSelectedObjectToScreen(thereRepository.getAllSceneNameModels());
|
};
|
||||||
thereRepository.fitCameraToCenteredObject(thereRepository.getAllSceneNameModels());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<div style={{ position: "absolute" }}>
|
||||||
|
{sceneMangerStore.sceneItems.map((el) => {
|
||||||
|
if (el instanceof StaticAssetItemModel) {
|
||||||
|
return StaticAssetModelView({ model: el });
|
||||||
|
}
|
||||||
|
return <></>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
<canvas ref={canvasRef} />
|
<canvas ref={canvasRef} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
70
ui/src/features/scene_manager/scene_manager_store.ts
Normal file
70
ui/src/features/scene_manager/scene_manager_store.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,9 @@ const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<>
|
<>
|
||||||
<SocketLister>
|
{/* <SocketLister>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</SocketLister>
|
</SocketLister> */}
|
||||||
|
<SceneManger />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue