forked from sent/waves
727 lines
27 KiB
JavaScript
727 lines
27 KiB
JavaScript
/**
|
|
* @file road.class.js
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
import Entity from '../framework/entity.class.js';
|
|
import ModelManager from '../framework/modelmanager.class.js';
|
|
|
|
import Utility from '../game/utility.class.js';
|
|
|
|
import * as THREE from '../lib/three.js/three.module.js';
|
|
|
|
export default class Road extends Entity {
|
|
static _init() {
|
|
Road.segmentsMax = 6;
|
|
|
|
const roadLightColor = 0xf1a134;
|
|
const roadLightIntensity = 2;
|
|
const roadLightConeOpacity = 0.02;
|
|
const roadLightDecay = 0.6;
|
|
|
|
const updateLightCones = root => {
|
|
root.traverse(obj => {
|
|
if (obj.material && obj.material.transparent) {
|
|
obj.material.opacity = roadLightConeOpacity;
|
|
obj.material.side = THREE.FrontSide;
|
|
}
|
|
});
|
|
};
|
|
|
|
Road._segmentTypes = new Map([
|
|
['Forest', {
|
|
count: Road.segmentsMax,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
segment.find('LightRoot').visible = !!options.useLight;
|
|
if (options.useLight) {
|
|
segment.light = environment.getPointLight(roadLightColor, roadLightIntensity, 4.3, roadLightDecay);
|
|
segment.find('LightContainer').add(segment.light);
|
|
updateLightCones(segment.find('LightRoot'));
|
|
}
|
|
},
|
|
}],
|
|
['ForestSign', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: ['SignPlace1', 'SignPlace2', 'SignPlace3'],
|
|
setup: (environment, segment, options) => {
|
|
segment.light = environment.getPointLight(roadLightColor, roadLightIntensity, 5.5, roadLightDecay);
|
|
segment.find('LightContainer').add(segment.light);
|
|
updateLightCones(segment.find('LightRoot'));
|
|
segment.find('SignPlace1').visible = options.sign === 'place_1';
|
|
segment.find('SignPlace2').visible = options.sign === 'place_2';
|
|
segment.find('SignPlace3').visible = options.sign === 'place_3';
|
|
},
|
|
}],
|
|
['ForestCrossing', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: ['Sign013', 'Sign014'],
|
|
setup: (environment, segment, options) => {
|
|
segment.find('LightRoot').visible = !!options.useLight;
|
|
if (options.useLight) {
|
|
segment.light = environment.getPointLight(roadLightColor, roadLightIntensity, 5, roadLightDecay);
|
|
segment.find('LightContainer').add(segment.light);
|
|
updateLightCones(segment.find('LightRoot'));
|
|
}
|
|
},
|
|
}],
|
|
['ForestBike', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
segment.light = environment.getPointLight(roadLightColor, roadLightIntensity, 4.3, roadLightDecay);
|
|
segment.find('LightContainer').add(segment.light);
|
|
updateLightCones(segment.find('LightRoot'));
|
|
segment.find('BloodPuddle').visible = !!options.blood;
|
|
segment.find('BloodTrail').visible = !!options.bloodTrail;
|
|
segment.find('BloodTrack').visible = !!options.bloodTrack;
|
|
segment.find('BloodPool').visible = !!options.bloodPool;
|
|
segment.find('Car').visible = !!options.car;
|
|
segment.find('Bicycle').visible = !!options.bike;
|
|
segment.find('Woman').visible = !!options.woman;
|
|
},
|
|
}],
|
|
['ForestResturant', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 7,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
segment.light = environment.getPointLight(
|
|
options.resturant === 'bleeding' ? 0xff0000 : 0xff6666,
|
|
options.resturant === 'open' ? 1 : 0.3, 10, 1);
|
|
segment.find('LightContainer').add(segment.light);
|
|
segment.find('ResturantOpen').visible = options.resturant === 'open';
|
|
segment.find('ResturantClosed').visible = options.resturant === 'closed';
|
|
segment.find('ResturantAbandoned').visible = options.resturant === 'abandoned';
|
|
segment.find('ResturantBleeding').visible = options.resturant === 'bleeding';
|
|
},
|
|
}],
|
|
['ForestField', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
segment.light = environment.getPointLight(0x001659, 2, 20, 1);
|
|
segment.find('LightContainer').add(segment.light);
|
|
},
|
|
}],
|
|
['ForestTree1', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['ForestTree2', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: -1.5,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['Bridge', {
|
|
count: Road.segmentsMax,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: ['SignDig'],
|
|
setup: (environment, segment, options) => {
|
|
segment.find('LightRoot').visible = !!options.useLight;
|
|
if (options.useLight) {
|
|
segment.light = environment.getPointLight(roadLightColor, roadLightIntensity, 4.3, roadLightDecay);
|
|
segment.find('LightContainer').add(segment.light);
|
|
updateLightCones(segment.find('LightRoot'));
|
|
}
|
|
segment.find('SignDig').visible = options.sign === 'dig';
|
|
},
|
|
}],
|
|
['BridgeCracks1', {
|
|
count: 2,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['BridgeCracks2', {
|
|
count: 2,
|
|
laneCenter: -2,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['BridgeCracks3', {
|
|
count: 2,
|
|
laneCenter: 2,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['BridgeCracks4', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['Tunnel', {
|
|
count: Road.segmentsMax,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: ['SignBike'],
|
|
setup: (environment, segment, options) => {
|
|
segment.find('LightRoot').visible = !!options.useLight;
|
|
if (options.useLight) {
|
|
segment.light = environment.getPointLight(roadLightColor, roadLightIntensity, 8, roadLightDecay);
|
|
segment.find('LightContainer').add(segment.light);
|
|
updateLightCones(segment.find('LightRoot'));
|
|
}
|
|
segment.find('SignBike').visible = options.sign === 'bike';
|
|
},
|
|
}],
|
|
['TunnelEntrance', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 1.5,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
segment.find('LightRoot').visible = !!options.useLight;
|
|
if (options.useLight) {
|
|
segment.light = environment.getPointLight(roadLightColor, roadLightIntensity, 8, roadLightDecay);
|
|
segment.find('LightContainer').add(segment.light);
|
|
updateLightCones(segment.find('LightRoot'));
|
|
}
|
|
segment.find('GiantGhost').visible = !!options.giant;
|
|
},
|
|
}],
|
|
['TunnelBarricade1', {
|
|
count: 1,
|
|
laneCenter: 2,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['TunnelBarricade2', {
|
|
count: 1,
|
|
laneCenter: -2,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['SmallTunnel', {
|
|
count: Road.segmentsMax,
|
|
laneCenter: 0,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['SmallTunnelEntrance', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
},
|
|
}],
|
|
['Tuonela', {
|
|
count: Road.segmentsMax,
|
|
laneCenter: 0,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
segment.light = environment.getPointLight(0xff0000, 0.5, 15, 2, 0, 5, 5);
|
|
segment.mesh.add(segment.light);
|
|
},
|
|
}],
|
|
['TuonelaThrone', {
|
|
count: 1,
|
|
laneCenter: 0,
|
|
laneOffset: 0,
|
|
nonDoubleSided: [],
|
|
setup: (environment, segment, options) => {
|
|
segment.light = environment.getPointLight(0xff6666, 5, 20, 2, 0, 5, 5);
|
|
segment.mesh.add(segment.light);
|
|
segment.find('Chains').visible = false;
|
|
},
|
|
}],
|
|
]);
|
|
|
|
Road._lengthPadding = 0.5;
|
|
|
|
Road._tmpV20 = new THREE.Vector2();
|
|
Road._tmpV30 = new THREE.Vector3();
|
|
Road._tmpV31 = new THREE.Vector3();
|
|
Road._tmpV32 = new THREE.Vector3();
|
|
Road._tmpRaycaster = new THREE.Raycaster();
|
|
Road._tmpArray = [];
|
|
}
|
|
|
|
constructor(context, scene, options = Object.create(null)) {
|
|
options.updatePriority = 11;
|
|
|
|
super(context, scene, options);
|
|
|
|
const tScene = scene.getThreeScene();
|
|
|
|
const gltfProps = ModelManager.getModel('props');
|
|
|
|
const findByPrefix = (root, prefix) => {
|
|
let found = null;
|
|
|
|
root.traverse(obj => {
|
|
if (!found && obj.name.indexOf(prefix) === 0) {
|
|
found = obj;
|
|
}
|
|
});
|
|
|
|
return found;
|
|
};
|
|
|
|
Road._segmentTypes.forEach((type, name) => {
|
|
const mesh = gltfProps.scene.getObjectByName(name);
|
|
|
|
const navMesh = findByPrefix(mesh, 'NavMesh').removeFromParent();
|
|
|
|
navMesh.geometry.computeBoundingBox();
|
|
|
|
mesh.traverse(obj => {
|
|
if (type.nonDoubleSided.includes(obj.name)) {
|
|
obj.material.side = THREE.FrontSide;
|
|
}
|
|
|
|
obj.material = Utility.replaceMaterial(obj.material, obj !== mesh);
|
|
|
|
obj.frustumCulled = false;
|
|
});
|
|
|
|
const instances = Array.from({ length: type.count }, (_, j) => {
|
|
const newMesh = mesh.clone();
|
|
newMesh.name = mesh.name + j;
|
|
newMesh.geometry = mesh.geometry.clone();
|
|
|
|
const newNavMesh = new THREE.Mesh(navMesh.geometry.clone(), new THREE.MeshBasicMaterial());
|
|
newNavMesh.name = navMesh.name + j;
|
|
|
|
tScene.add(newMesh);
|
|
|
|
newNavMesh.visible = false;
|
|
|
|
const segment = {
|
|
type,
|
|
index: null,
|
|
|
|
find: prefix => findByPrefix(newMesh, prefix),
|
|
|
|
handleEntered: null,
|
|
rotation: 0,
|
|
heightOffset: 0,
|
|
nearScaleX: 1,
|
|
farScaleX: 1,
|
|
nearOffsetX: 0,
|
|
position: new THREE.Vector3(),
|
|
angle: 0,
|
|
|
|
light: null,
|
|
|
|
meshOriginal: mesh,
|
|
mesh: newMesh,
|
|
|
|
navMeshOriginal: navMesh,
|
|
navMesh: newNavMesh,
|
|
|
|
remove: () => {
|
|
if (segment.light !== null) {
|
|
segment.light.pool();
|
|
segment.light = null;
|
|
}
|
|
|
|
segment.mesh.removeFromParent();
|
|
segment.navMesh.removeFromParent();
|
|
|
|
this._segments[segment.index] = undefined;
|
|
|
|
segment.index = null;
|
|
},
|
|
|
|
twistPosition: position => {
|
|
const distance = -position.z / type.length;
|
|
const scaleX = segment.nearScaleX * (1 - distance) + segment.farScaleX * distance;
|
|
const offsetX = segment.nearOffsetX * (1 - distance);
|
|
|
|
position.x = (position.x + offsetX) * scaleX;
|
|
|
|
const lastSegment = this._segments[segment.index - 1];
|
|
if (lastSegment === undefined) {
|
|
position.y += distance * segment.heightOffset;
|
|
} else {
|
|
const y0 = position.y + distance * lastSegment.heightOffset;
|
|
const y1 = position.y + distance * segment.heightOffset;
|
|
const t = Utility.easeInOutCubic(THREE.MathUtils.clamp(distance, 0, 1));
|
|
|
|
position.y = y0 * (1 - t) + y1 * t;
|
|
}
|
|
|
|
if (segment.rotation !== 0) {
|
|
const radius = type.length / segment.rotation;
|
|
|
|
const x = position.x - radius;
|
|
position.z = x * Math.sin(segment.rotation * distance);
|
|
position.x = radius + x * Math.cos(segment.rotation * distance);
|
|
}
|
|
|
|
return position;
|
|
},
|
|
toRoadPosition: (position, target) => {
|
|
const local = segment.navMesh.worldToLocal(Road._tmpV30.copy(position));
|
|
|
|
if (segment.rotation === 0) {
|
|
const distance = -local.z / type.length;
|
|
const scaleX = segment.nearScaleX * (1 - distance) + segment.farScaleX * distance;
|
|
const offsetX = segment.nearOffsetX * (1 - distance);
|
|
|
|
return target.set(local.x / scaleX - offsetX, THREE.MathUtils.clamp(distance, 0, 1));
|
|
}
|
|
|
|
const centerRadius = type.length / segment.rotation;
|
|
const dx = local.x - centerRadius;
|
|
const radius = Math.sqrt(dx * dx + local.z * local.z);
|
|
const x = centerRadius + (segment.rotation < 0 ? radius : -radius);
|
|
|
|
const distance = Math.asin(-local.z / radius) / Math.abs(segment.rotation);
|
|
const scaleX = segment.nearScaleX * (1 - distance) + segment.farScaleX * distance;
|
|
const offsetX = segment.nearOffsetX * (1 - distance);
|
|
|
|
return target.set(x / scaleX - offsetX, THREE.MathUtils.clamp(distance, 0, 1));
|
|
},
|
|
};
|
|
|
|
return segment;
|
|
});
|
|
|
|
type.name = name;
|
|
type.length = -navMesh.geometry.boundingBox.min.z - Road._lengthPadding;
|
|
type.instances = instances;
|
|
type.getNextInstance = () => {
|
|
instances.push(instances.shift());
|
|
|
|
return instances[0];
|
|
};
|
|
});
|
|
|
|
this._segments = [];
|
|
this._activeSegments = [];
|
|
|
|
this._playerRoadPosition = new THREE.Vector2();
|
|
|
|
this._nextSegmentIndex = null;
|
|
this._lastEnteredSegmentIndex = null;
|
|
|
|
this._lastRaycastSegment = null;
|
|
}
|
|
|
|
reset(context, nextSegmentIndex, lastEnteredSegmentIndex) {
|
|
this._nextSegmentIndex = nextSegmentIndex;
|
|
this._lastEnteredSegmentIndex = lastEnteredSegmentIndex;
|
|
this._playerRoadPosition.set(0, lastEnteredSegmentIndex + 0.5);
|
|
|
|
Road._segmentTypes.forEach(type => {
|
|
type.instances.forEach(segment => {
|
|
if (segment.index === null) {
|
|
segment.mesh.removeFromParent();
|
|
segment.mesh.traverse(obj => {
|
|
obj.frustumCulled = true;
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
getSegmentInstance(typeName) {
|
|
return Road._segmentTypes.get(typeName).getNextInstance();
|
|
}
|
|
|
|
getNextSegmentIndex() {
|
|
return this._nextSegmentIndex;
|
|
}
|
|
|
|
roadToWorldPosition(position, target) {
|
|
const segment = this._segments[Math.floor(position.y)];
|
|
if (segment === undefined) {
|
|
return target.set(0, 0, 0);
|
|
}
|
|
const local = target.set(position.x, 0, -segment.type.length * (position.y % 1));
|
|
return segment.mesh.localToWorld(segment.twistPosition(local));
|
|
}
|
|
|
|
getRoadSafeRespawnDistance(fromDistance, toDistance = fromDistance + 2, step = 0.02) {
|
|
for (let distance = fromDistance; distance <= toDistance; distance += step) {
|
|
const x = this.getRoadType(distance).laneOffset;
|
|
const position = this.roadToWorldPosition(Road._tmpV20.set(x, distance), Road._tmpV30);
|
|
|
|
if (this.getHeightAtPosition(position) !== null) {
|
|
return distance;
|
|
}
|
|
}
|
|
|
|
return fromDistance;
|
|
}
|
|
|
|
getRoadAngle(distance) {
|
|
const segment = this._segments[Math.floor(distance)];
|
|
return segment === undefined ? 0 : segment.angle + segment.rotation * (distance % 1) + Math.PI;
|
|
}
|
|
|
|
getRoadType(distance) {
|
|
const segment = this._segments[Math.floor(distance)];
|
|
return segment === undefined ? Road._segmentTypes.get('Forest') : segment.type;
|
|
}
|
|
|
|
getRoadDistanceMin() {
|
|
return this._nextSegmentIndex - this._activeSegments.length + 1e-10;
|
|
}
|
|
|
|
getRoadDistanceMax() {
|
|
return this._nextSegmentIndex - 1e-10;
|
|
}
|
|
|
|
getPlayerRoadPosition() {
|
|
return this._playerRoadPosition;
|
|
}
|
|
|
|
getRoadDistanceBetween(d0, d1) {
|
|
const dMin = d0 < d1 ? d0 : d1;
|
|
const dMax = d0 < d1 ? d1 : d0;
|
|
|
|
const iMin = Math.floor(dMin);
|
|
const iMax = Math.floor(dMax);
|
|
|
|
let distance = 0;
|
|
|
|
for (let i = iMin; i <= iMax; i++) {
|
|
const segment = this._segments[i];
|
|
const length = segment === undefined ? 13 : segment.type.length;
|
|
|
|
if (i === iMin && i === iMax) {
|
|
distance += (dMax - dMin) * length;
|
|
} else if (i === iMin) {
|
|
distance += (1 - dMin + iMin) * length;
|
|
} else if (i === iMax) {
|
|
distance += (dMax - iMax) * length;
|
|
} else {
|
|
distance += length;
|
|
}
|
|
}
|
|
|
|
return d0 < d1 ? distance : -distance;
|
|
}
|
|
|
|
getForwardSlopeAngle(object3d, distance = 1, samples = 2, maxDeltaHeight = 1) {
|
|
const step = distance * 2 / (samples - 1);
|
|
const start = object3d.localToWorld(Road._tmpV30.set(0, 0, -distance));
|
|
const end = object3d.localToWorld(Road._tmpV31.set(0, 0, distance));
|
|
|
|
let sum = 0;
|
|
let count = 0;
|
|
let lastHeight = null;
|
|
|
|
for (let i = 0; i < samples; i++) {
|
|
const position = Road._tmpV32.copy(start).lerp(end, i / (samples - 1));
|
|
const height = this.getHeightAtPosition(position, false);
|
|
if (lastHeight !== null && Math.abs(lastHeight - height) < maxDeltaHeight) {
|
|
sum += (height - lastHeight) / step;
|
|
count++;
|
|
}
|
|
lastHeight = height;
|
|
}
|
|
|
|
return count === 0 ? 0 : Math.atan(sum / count);
|
|
}
|
|
|
|
getHeightAtPosition(position, isPlayer = false) {
|
|
const raycaster = Road._tmpRaycaster;
|
|
|
|
raycaster.ray.origin.set(position.x, 100000, position.z);
|
|
raycaster.ray.direction.set(0, -1, 0);
|
|
|
|
const intersects = Road._tmpArray;
|
|
intersects.length = 0;
|
|
|
|
for (let i = -1; i < this._activeSegments.length; i++) {
|
|
let segment;
|
|
if (i === -1) {
|
|
if (!this._activeSegments.includes(this._lastRaycastSegment)) {
|
|
continue;
|
|
}
|
|
segment = this._lastRaycastSegment;
|
|
} else {
|
|
segment = this._activeSegments[i]
|
|
if (segment === this._lastRaycastSegment) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
segment.navMesh.raycast(raycaster, intersects);
|
|
|
|
if (intersects.length === 0) {
|
|
continue;
|
|
}
|
|
|
|
const intersect = intersects[0];
|
|
|
|
if (isPlayer) {
|
|
segment.toRoadPosition(intersect.point, this._playerRoadPosition);
|
|
const distance = this._playerRoadPosition.y;
|
|
this._playerRoadPosition.y += segment.index;
|
|
|
|
if (segment.index > this._lastEnteredSegmentIndex &&
|
|
distance >= segment.enteredDistance) {
|
|
this._lastEnteredSegmentIndex = segment.index;
|
|
|
|
if (segment.handleEntered !== null) {
|
|
segment.handleEntered();
|
|
}
|
|
}
|
|
}
|
|
|
|
this._lastRaycastSegment = segment;
|
|
|
|
return intersect.point.y;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
clearSegments() {
|
|
this._activeSegments.forEach(segment => {
|
|
segment.remove();
|
|
});
|
|
this._activeSegments.length = 0;
|
|
}
|
|
|
|
createSegment(name, rotation = 0, heightOffset = 0, options = Object.create(null),
|
|
nearScaleX = 1, farScaleX = 1, nearOffsetX = 0) {
|
|
const tScene = this.getScene().getThreeScene();
|
|
const type = Road._segmentTypes.get(name);
|
|
|
|
if (this._activeSegments.length >= Road.segmentsMax) {
|
|
this._activeSegments.shift().remove();
|
|
}
|
|
|
|
const lastSegment = this._activeSegments.length > 0 ? this._activeSegments[this._activeSegments.length - 1] : null;
|
|
const segment = type.getNextInstance();
|
|
if (this._activeSegments.includes(segment)) {
|
|
throw new Error(`No more segments of type "${name}`);
|
|
}
|
|
this._activeSegments.push(segment);
|
|
|
|
segment.index = this._nextSegmentIndex;
|
|
this._segments[this._nextSegmentIndex++] = segment;
|
|
|
|
tScene.add(segment.mesh);
|
|
tScene.add(segment.navMesh);
|
|
|
|
segment.handleEntered = options.handleEntered === undefined ? null : options.handleEntered;
|
|
segment.enteredDistance = options.enteredDistance === undefined ? 0 : options.enteredDistance;
|
|
segment.rotation = rotation;
|
|
segment.heightOffset = heightOffset;
|
|
segment.nearScaleX = nearScaleX;
|
|
segment.farScaleX = farScaleX;
|
|
segment.nearOffsetX = nearOffsetX;
|
|
|
|
if (lastSegment === null) {
|
|
segment.position.set(0, 100, 0);
|
|
segment.angle = 0;
|
|
} else {
|
|
lastSegment.mesh.localToWorld(lastSegment.twistPosition(
|
|
segment.position.set(0, 0, -lastSegment.type.length)));
|
|
segment.angle = lastSegment.angle + lastSegment.rotation;
|
|
}
|
|
|
|
segment.mesh.position.copy(segment.position);
|
|
segment.mesh.rotation.set(0, -segment.angle, 0);
|
|
|
|
segment.navMesh.position.copy(segment.position);
|
|
segment.navMesh.rotation.set(0, -segment.angle, 0);
|
|
|
|
segment.mesh.updateMatrix();
|
|
segment.navMesh.updateMatrix();
|
|
|
|
segment.mesh.updateWorldMatrix(false, false);
|
|
segment.navMesh.updateWorldMatrix(false, false);
|
|
|
|
const radius = rotation === 0 ? 0 : type.length / rotation;
|
|
|
|
[[segment.meshOriginal.geometry, segment.mesh.geometry],
|
|
[segment.navMeshOriginal.geometry, segment.navMesh.geometry]].forEach(([geometry1, geometry2]) => {
|
|
const original = geometry1.attributes.position;
|
|
const position = geometry2.attributes.position;
|
|
|
|
position.needsUpdate = true;
|
|
|
|
for (let i = 0; i < position.count; i++) {
|
|
const point = segment.twistPosition(Road._tmpV30.set(
|
|
original.getX(i), original.getY(i), original.getZ(i)));
|
|
|
|
position.setXYZ(i, point.x, point.y, point.z);
|
|
}
|
|
|
|
geometry2.computeBoundingSphere();
|
|
geometry2.computeBoundingBox();
|
|
});
|
|
|
|
segment.mesh.geometry.computeVertexNormals();
|
|
|
|
segment.mesh.children.forEach(obj => {
|
|
const objOriginal = segment.meshOriginal.getObjectByName(obj.name);
|
|
if (objOriginal === undefined) {
|
|
return;
|
|
}
|
|
|
|
obj.position.copy(objOriginal.position);
|
|
obj.rotation.copy(objOriginal.rotation);
|
|
obj.updateMatrix();
|
|
|
|
const distance = segment.toRoadPosition(obj.getWorldPosition(Road._tmpV30), Road._tmpV20).y;
|
|
|
|
segment.twistPosition(obj.position.copy(objOriginal.position));
|
|
|
|
obj.rotateY(segment.rotation * -distance + Math.PI * 2);
|
|
obj.updateMatrix();
|
|
});
|
|
|
|
segment.mesh.traverse(obj => {
|
|
obj.updateMatrix();
|
|
|
|
if (obj.material !== undefined) {
|
|
obj.renderOrder = this._nextSegmentIndex;
|
|
}
|
|
});
|
|
|
|
const environment = this.getScene().findEntityOfType('Environment');
|
|
|
|
segment.type.setup(environment, segment, options);
|
|
|
|
return segment;
|
|
}
|
|
}
|
|
|
|
Road._init();
|
|
Road.p_register();
|