857 rover example

This commit is contained in:
Zemledelec 2025-07-28 17:01:16 +04:00
parent b6167c5a65
commit a0c8256dfd
12 changed files with 394 additions and 47 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -32,6 +32,178 @@ import {
Easing Easing
} from "../../lib/og.es.js"; } from "../../lib/og.es.js";
// Создаем HTML интерфейс для слайдеров
function createSliderControls() {
const controlsContainer = document.createElement('div');
controlsContainer.style.cssText = `
position: absolute;
top: 10px;
left: 10px;
background: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 8px;
color: white;
font-family: Arial, sans-serif;
z-index: 1000;
`;
// Roll слайдер для suspLeftFront
const rollContainer = document.createElement('div');
rollContainer.style.marginBottom = '15px';
const rollLabel = document.createElement('label');
rollLabel.textContent = 'SuspLeftFront Roll (-90° - 90°): ';
rollLabel.style.display = 'block';
rollLabel.style.marginBottom = '5px';
const rollSlider = document.createElement('input');
rollSlider.type = 'range';
rollSlider.min = '-90';
rollSlider.max = '90';
rollSlider.value = '0';
rollSlider.step = '0.1';
rollSlider.id = 'rollSlider';
rollSlider.style.width = '200px';
const rollValue = document.createElement('span');
rollValue.id = 'rollValue';
rollValue.textContent = '0°';
rollValue.style.marginLeft = '10px';
rollContainer.appendChild(rollLabel);
rollContainer.appendChild(rollSlider);
rollContainer.appendChild(rollValue);
// Pitch слайдер для suspLeftFront
const pitchContainer = document.createElement('div');
pitchContainer.style.marginBottom = '15px';
const pitchLabel = document.createElement('label');
pitchLabel.textContent = 'SuspLeftFront Pitch (-45° - 45°): ';
pitchLabel.style.display = 'block';
pitchLabel.style.marginBottom = '5px';
const pitchSlider = document.createElement('input');
pitchSlider.type = 'range';
pitchSlider.min = '-45';
pitchSlider.max = '45';
pitchSlider.value = '13';
pitchSlider.step = '0.1';
pitchSlider.id = 'pitchSlider';
pitchSlider.style.width = '200px';
const pitchValue = document.createElement('span');
pitchValue.id = 'pitchValue';
pitchValue.textContent = '13°';
pitchValue.style.marginLeft = '10px';
pitchContainer.appendChild(pitchLabel);
pitchContainer.appendChild(pitchSlider);
pitchContainer.appendChild(pitchValue);
// Yaw слайдер для amortLeftFront
const yawContainer = document.createElement('div');
yawContainer.style.marginBottom = '15px';
const yawLabel = document.createElement('label');
yawLabel.textContent = 'AmortLeftFront Roll (-180° - 180°): ';
yawLabel.style.display = 'block';
yawLabel.style.marginBottom = '5px';
const yawSlider = document.createElement('input');
yawSlider.type = 'range';
yawSlider.min = '-180';
yawSlider.max = '180';
yawSlider.value = '0';
yawSlider.step = '0.1';
yawSlider.id = 'yawSlider';
yawSlider.style.width = '200px';
const yawValue = document.createElement('span');
yawValue.id = 'yawValue';
yawValue.textContent = '0°';
yawValue.style.marginLeft = '10px';
yawContainer.appendChild(yawLabel);
yawContainer.appendChild(yawSlider);
yawContainer.appendChild(yawValue);
// Roll слайдер для suspLeftBack
const rollBackContainer = document.createElement('div');
rollBackContainer.style.marginBottom = '15px';
const rollBackLabel = document.createElement('label');
rollBackLabel.textContent = 'SuspLeftBack Roll (-45° - 45°): ';
rollBackLabel.style.display = 'block';
rollBackLabel.style.marginBottom = '5px';
const rollBackSlider = document.createElement('input');
rollBackSlider.type = 'range';
rollBackSlider.min = '-45';
rollBackSlider.max = '45';
rollBackSlider.value = '0';
rollBackSlider.step = '0.1';
rollBackSlider.id = 'rollBackSlider';
rollBackSlider.style.width = '200px';
const rollBackValue = document.createElement('span');
rollBackValue.id = 'rollBackValue';
rollBackValue.textContent = '0°';
rollBackValue.style.marginLeft = '10px';
rollBackContainer.appendChild(rollBackLabel);
rollBackContainer.appendChild(rollBackSlider);
rollBackContainer.appendChild(rollBackValue);
// Yaw слайдер для amortLeftBack
const yawBackContainer = document.createElement('div');
yawBackContainer.style.marginBottom = '15px';
const yawBackLabel = document.createElement('label');
yawBackLabel.textContent = 'AmortLeftBack Yaw (-90° - 90°): ';
yawBackLabel.style.display = 'block';
yawBackLabel.style.marginBottom = '5px';
const yawBackSlider = document.createElement('input');
yawBackSlider.type = 'range';
yawBackSlider.min = '-180';
yawBackSlider.max = '180';
yawBackSlider.value = '0';
yawBackSlider.step = '0.1';
yawBackSlider.id = 'yawBackSlider';
yawBackSlider.style.width = '200px';
const yawBackValue = document.createElement('span');
yawBackValue.id = 'yawBackValue';
yawBackValue.textContent = '0°';
yawBackValue.style.marginLeft = '10px';
yawBackContainer.appendChild(yawBackLabel);
yawBackContainer.appendChild(yawBackSlider);
yawBackContainer.appendChild(yawBackValue);
controlsContainer.appendChild(rollContainer);
controlsContainer.appendChild(pitchContainer);
controlsContainer.appendChild(yawContainer);
controlsContainer.appendChild(rollBackContainer);
controlsContainer.appendChild(yawBackContainer);
document.body.appendChild(controlsContainer);
return {
rollSlider,
pitchSlider,
yawSlider,
rollBackSlider,
yawBackSlider,
rollValue,
pitchValue,
yawValue,
rollBackValue,
yawBackValue
};
}
let renderer = new Renderer("frame", { let renderer = new Renderer("frame", {
msaa: 8, msaa: 8,
controls: [new control.SimpleNavigation({ speed: 0.01 }), new control.GeoObjectEditor()], controls: [new control.SimpleNavigation({ speed: 0.01 }), new control.GeoObjectEditor()],
@ -43,7 +215,7 @@ class MyScene extends RenderNode {
super("MyScene"); super("MyScene");
} }
init() { async init() {
let collection = new EntityCollection({ let collection = new EntityCollection({
entities: [] entities: []
@ -51,40 +223,174 @@ class MyScene extends RenderNode {
collection.addTo(this); collection.addTo(this);
const cameraPositions = [ this.renderer.activeCamera.set(new Vec3(10, 11, 13), new Vec3(0, 2, 2));
[new Vec3(20, 21, 23), new Vec3(0, 2, 2), Vec3.UP, Easing.ElasticOut],
[new Vec3(40, 10, 15), new Vec3(10, 0, 0), Vec3.LEFT, Easing.ElasticOut],
[new Vec3(10, 30, 45), new Vec3(10, 0, 0), Vec3.DOWN, Easing.CubicInOut],
[new Vec3(40, 10, 15), new Vec3(10, 10, 0), Vec3.RIGHT, Easing.BackInOut],
];
let i = 0;
setInterval(() => {
this.renderer.activeCamera.flyCartesian(cameraPositions[i][0], {
look: cameraPositions[i][1],
up: cameraPositions[i][2],
ease: cameraPositions[i][3],
});
i = (i + 1) % cameraPositions.length;
}, 1500);
console.log(this.renderer.activeCamera);
DracoDecoderModule().then((decoderModule) => { let base = new Entity();
Gltf.connectDracoDecoderModule(decoderModule); window.base = base;
Gltf.loadGlb("./maxwell_the_cat.glb").then((gltf) => {
const entities = gltf.toEntities(); Gltf.loadGlb("./rover_base.glb").then((gltf) => {
const cat = entities[0]; const entities = gltf.toEntities();
cat.setScale(0.5); for (let i = 0; i < entities.length; i++) {
cat.setPitch(-90 * (Math.PI / 180)); base.appendChild(entities[i]);
collection.add(cat); }
});
}); });
Gltf.loadGlb("./f22.glb").then((gltf) => { let suspLeftFront = new Entity({
const entity = gltf.toEntities()[0]; cartesian: new Vec3(0.26, -0.0, -0.78),
entity.setScale(1); pitch: 13 * Math.PI / 180,
entity.setCartesian(20, 5, 0); relativePosition: true,
entity.setPitch(-90 * (Math.PI / 180)); });
collection.add(entity);
window.suspLeftFront = suspLeftFront;
Gltf.loadGlb("./susp_left_front.glb").then((gltf) => {
const entities = gltf.toEntities();
for (let i = 0; i < entities.length; i++) {
entities[i].relativePosition = true;
suspLeftFront.appendChild(entities[i]);
}
});
let amortLeftFront = new Entity({
cartesian: new Vec3(0.876, -0.3, -0.26),
relativePosition: true,
pitch: -103 * Math.PI / 180,
});
window.amortLeftFront = amortLeftFront;
Gltf.loadGlb("./amort_left_front.glb").then((gltf) => {
const entities = gltf.toEntities();
amortLeftFront.appendChildren(entities, true);
});
suspLeftFront.appendChild(amortLeftFront);
let suspLeftBack = new Entity({
cartesian: new Vec3(-0.757, -0.222, -0.008),
relativePosition: true,
pitch: -13 * Math.PI / 180
});
window.suspLeftBack = suspLeftBack;
Gltf.loadGlb("./susp_left_back.glb").then((gltf) => {
const entities = gltf.toEntities();
for (let i = 0; i < entities.length; i++) {
entities[i].relativePosition = true;
suspLeftBack.appendChild(entities[i]);
}
});
suspLeftFront.appendChild(suspLeftBack);
let amortLeftBack = new Entity({
cartesian: new Vec3(-0.625, -0.01, -0.263),
relativePosition: true,
});
window.amortLeftBack = amortLeftBack;
Gltf.loadGlb("./amort_left_back.glb").then((gltf) => {
const entities = gltf.toEntities()[0];
entities.relativePosition = true;
amortLeftBack.appendChild(entities)
});
suspLeftBack.appendChild(amortLeftBack);
suspLeftFront.appendChild(amortLeftFront);
base.appendChild(suspLeftFront);
let wheelFrontLeft = new Entity({
cartesian: new Vec3(0, -0.05, -0.395),
relativePosition: true,
pitch: 90 * Math.PI / 180
});
let wheelBackLeft = new Entity({
cartesian: new Vec3(0, -0.392, 0.065),
relativePosition: true,
});
let wheelMiddleLeft = new Entity({
cartesian: new Vec3(0.45, -0.4, -0.3),
relativePosition: true,
});
Gltf.loadGlb("./wheel_left.glb").then((gltf) => {
const entities = gltf.toEntities()[0];
entities.relativePosition = true;
wheelFrontLeft.appendChild(entities);
});
Gltf.loadGlb("./wheel_left.glb").then((gltf) => {
const entities = gltf.toEntities()[0];
entities.relativePosition = true;
wheelBackLeft.appendChild(entities);
});
Gltf.loadGlb("./wheel_left.glb").then((gltf) => {
const entities = gltf.toEntities()[0];
entities.relativePosition = true;
wheelMiddleLeft.appendChild(entities);
});
amortLeftFront.appendChild(wheelFrontLeft);
amortLeftBack.appendChild(wheelBackLeft);
suspLeftBack.appendChild(wheelMiddleLeft);
let wheelRoll = 0;
this.renderer.events.on("draw", () => {
wheelFrontLeft.setRoll(wheelRoll * Math.PI / 180);
wheelBackLeft.setRoll(wheelRoll * Math.PI / 180);
wheelMiddleLeft.setRoll(wheelRoll * Math.PI / 180);
wheelRoll -= 0.3;
})
collection.add(base);
// Создаем слайдеры после загрузки модели
const sliders = createSliderControls();
// Настройка обработчиков событий для слайдеров
sliders.rollSlider.addEventListener('input', (e) => {
const rollDegrees = parseFloat(e.target.value);
const rollRadians = rollDegrees * (Math.PI / 180);
suspLeftFront.setRoll(rollRadians);
sliders.rollValue.textContent = rollDegrees.toFixed(1) + '°';
});
sliders.pitchSlider.addEventListener('input', (e) => {
const pitchDegrees = parseFloat(e.target.value);
const pitchRadians = pitchDegrees * (Math.PI / 180);
suspLeftFront.setPitch(pitchRadians);
sliders.pitchValue.textContent = pitchDegrees.toFixed(1) + '°';
});
sliders.yawSlider.addEventListener('input', (e) => {
const yawDegrees = parseFloat(e.target.value);
const yawRadians = yawDegrees * (Math.PI / 180);
//amortLeftFront.childEntities[0].childEntities[0].setYaw(yawRadians);
amortLeftFront.setRoll(yawRadians);
sliders.yawValue.textContent = yawDegrees.toFixed(1) + '°';
});
sliders.rollBackSlider.addEventListener('input', (e) => {
const rollBackDegrees = parseFloat(e.target.value);
const rollBackRadians = rollBackDegrees * (Math.PI / 180);
suspLeftBack.setRoll(rollBackRadians);
sliders.rollBackValue.textContent = rollBackDegrees.toFixed(1) + '°';
});
sliders.yawBackSlider.addEventListener('input', (e) => {
const yawBackDegrees = parseFloat(e.target.value);
const yawBackRadians = yawBackDegrees * (Math.PI / 180);
amortLeftBack.setYaw(yawBackRadians);
sliders.yawBackValue.textContent = yawBackDegrees.toFixed(1) + '°';
}); });
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -117,6 +117,8 @@ class Entity {
static __counter__: number = 0; static __counter__: number = 0;
protected _name: string;
/** /**
* Uniq identifier. * Uniq identifier.
* @public * @public
@ -311,9 +313,11 @@ class Entity {
this.__id = Entity.__counter__++; this.__id = Entity.__counter__++;
this._name = options.name || `entity:${this.__id}`;
this.properties = options.properties || {}; this.properties = options.properties || {};
this.properties.name = this.properties.name != undefined ? this.properties.name : ""; //this.properties.name = this.properties.name != undefined ? this.properties.name : "";
this.childEntities = []; this.childEntities = [];
@ -395,6 +399,17 @@ class Entity {
} }
public get name(): string {
return this._name;
}
public set name(name: string) {
if (name !== this._name) {
this._name = name;
//ec && ec.events.dispatch(ec.events.entityname, this);
}
}
public get isEmpty(): boolean { public get isEmpty(): boolean {
return !(this.strip return !(this.strip
|| this.polyline || this.polyline
@ -721,6 +736,9 @@ class Entity {
this._rollRad = this._qRot.getRoll(); this._rollRad = this._qRot.getRoll();
this._updateAbsolutePosition(); this._updateAbsolutePosition();
// ?
//this._useDirectQuaternion = false;
} }
/** /**
@ -971,7 +989,13 @@ class Entity {
this._rootCartesian.copy(parent._rootCartesian); this._rootCartesian.copy(parent._rootCartesian);
if (!this._useDirectQuaternion) { if (!this._useDirectQuaternion) {
this._qRot.setPitchYawRoll(this._pitchRad, this._yawRad, this._rollRad); //this._qRot.setPitchYawRoll(this._pitchRad, this._yawRad, this._rollRad);
if (parent && this.forceGlobalRotation) {
this._qRot.setPitchYawRoll(parent._pitchRad, parent._yawRad, parent._rollRad);
} else {
this._qRot.setPitchYawRoll(this._pitchRad, this._yawRad, this._rollRad);
}
} }
parent._absoluteQRot.mulRes(this._qRot, this._absoluteQRot); parent._absoluteQRot.mulRes(this._qRot, this._absoluteQRot);
@ -1305,6 +1329,21 @@ class Entity {
return null; return null;
} }
/**
* Append child entity.
* @public
* @param {Entity[]} entities - Child entities.
* @param {boolean} [forceRelativePosition] - Force relative position property.
*/
public appendChildren(entities: Entity[], forceRelativePosition?: boolean) {
for (let i = 0; i < entities.length; i++) {
if (forceRelativePosition !== undefined) {
entities[i].relativePosition = forceRelativePosition;
}
this.appendChild(entities[i]);
}
}
/** /**
* Append child entity. * Append child entity.
* @public * @public

View File

@ -1,10 +1,10 @@
import { DecoderModule } from "draco3d"; import {DecoderModule} from "draco3d";
import { Entity } from "../../entity"; import {Entity} from "../../entity";
import { Quat } from "../../math/Quat"; import {Quat} from "../../math/Quat";
import { Vec3 } from "../../math/Vec3"; import {Vec3} from "../../math/Vec3";
import { Mat4, NumberArray16 } from "../../math/Mat4"; import {Mat4, NumberArray16} from "../../math/Mat4";
import { Object3d } from "../../Object3d"; import {Object3d} from "../../Object3d";
import { Glb } from "./glbParser"; import {Glb} from "./glbParser";
import { import {
Accessor, Accessor,
AccessorComponentType, AccessorComponentType,
@ -23,9 +23,11 @@ import {
export class Gltf { export class Gltf {
private static _dracoDecoderModule: DecoderModule | null = null; private static _dracoDecoderModule: DecoderModule | null = null;
public static connectDracoDecoderModule(decoder: any): void { public static connectDracoDecoderModule(decoder: any): void {
Gltf._dracoDecoderModule = decoder; Gltf._dracoDecoderModule = decoder;
} }
public static async loadGlb(url: string) { public static async loadGlb(url: string) {
const data = await Glb.load(url); const data = await Glb.load(url);
return new Gltf(data); return new Gltf(data);
@ -79,7 +81,7 @@ export class Gltf {
private _nodeToEntity(node: GltfNode, parent?: Entity): Entity { private _nodeToEntity(node: GltfNode, parent?: Entity): Entity {
const entity = new Entity({ const entity = new Entity({
name: `node_${node.name}`, name: node.name,
cartesian: new Vec3(0, 0, 0), cartesian: new Vec3(0, 0, 0),
relativePosition: parent !== undefined, relativePosition: parent !== undefined,
}); });
@ -197,7 +199,7 @@ export class Gltf {
const source = const source =
this.gltf.gltf.textures[ this.gltf.gltf.textures[
material.pbrMetallicRoughness.baseColorTexture.index material.pbrMetallicRoughness.baseColorTexture.index
].source; ].source;
if (source !== undefined) { if (source !== undefined) {
mat.baseColorTexture = { mat.baseColorTexture = {
image: this._images[source], image: this._images[source],
@ -209,12 +211,12 @@ export class Gltf {
const source = const source =
this.gltf.gltf.textures[ this.gltf.gltf.textures[
material.pbrMetallicRoughness.metallicRoughnessTexture.index material.pbrMetallicRoughness.metallicRoughnessTexture.index
].source; ].source;
if (source !== undefined) { if (source !== undefined) {
mat.metallicRoughnessTexture = { mat.metallicRoughnessTexture = {
image: this._images[source], image: this._images[source],
texCoord: texCoord:
material.pbrMetallicRoughness.metallicRoughnessTexture.texCoord material.pbrMetallicRoughness.metallicRoughnessTexture.texCoord
}; };
} }
} }
@ -344,7 +346,7 @@ export class Gltf {
draco.destroy(decoder); draco.destroy(decoder);
primitive = { primitive = {
name: `${meshData.name}_${material.name}_${index}`, name: `${meshData.name}/${material.name}/${index}`,
vertices: attributes.POSITION, vertices: attributes.POSITION,
indices: indices, indices: indices,
mode: primitiveData.mode ? primitiveData.mode : PrimitiveMode.triangles, mode: primitiveData.mode ? primitiveData.mode : PrimitiveMode.triangles,
@ -358,7 +360,7 @@ export class Gltf {
? this.gltf.gltf.accessors[texcoordAccessorKey] ? this.gltf.gltf.accessors[texcoordAccessorKey]
: undefined; : undefined;
primitive = { primitive = {
name: `${meshData.name}_${material.name}_${index}`, name: `${meshData.name}/${material.name}/${index}`,
indices: primitiveData.indices indices: primitiveData.indices
? Gltf._access(this.gltf.gltf.accessors[primitiveData.indices], this.gltf) ? Gltf._access(this.gltf.gltf.accessors[primitiveData.indices], this.gltf)
: undefined, : undefined,