diff --git a/src/services/spacingEditor.ts b/src/services/spacingEditor.ts index 149954b..71a28db 100644 --- a/src/services/spacingEditor.ts +++ b/src/services/spacingEditor.ts @@ -3,7 +3,7 @@ import * as Log from "../utilities/logger"; import { getTileByCoords } from "../utilities/map"; import { abs } from "../utilities/math"; import { isNull, isUndefined } from "../utilities/type"; -import { getSubpositionTranslationDistance, getTrackDistances, TrackDistances } from "./subpositionHelper"; +import { getSubpositionTranslationDistance, getTrackSegmentDistances, TrackDistances } from "./subpositionHelper"; const MaxForwardIterations = 10; @@ -44,8 +44,10 @@ export function getDistanceFromProgress(car: Car, trackProgress: number): number ? new ForwardIterator(trackProgress, currentProgress) : new BackwardIterator(abs(trackProgress), currentProgress); + Log.debug("Iterating", (trackProgress >= 0)?"foward":"backward", "from progress", currentProgress, "to", trackProgress); + let trackPosition: CoordsXYZD = currentTrackLocation; - let trackDistances = getTrackDistances(iteratorSegment, subposition, trackPosition.direction); + let trackDistances = getTrackSegmentDistances(iteratorSegment, subposition, trackPosition.direction); subpositionIterator._setInitialDistanceFromCarRemainingDistance(car.remainingDistance); while (subpositionIterator._remainingProgress > 0 && iteratorSegment) @@ -79,7 +81,7 @@ export function getDistanceFromProgress(car: Car, trackProgress: number): number } const nextTrackPosition = iterator.position; - const nextTrackDistance = getTrackDistances(iteratorSegment, subposition, nextTrackPosition.direction); + const nextTrackDistance = getTrackSegmentDistances(iteratorSegment, subposition, nextTrackPosition.direction); subpositionIterator._remainingProgress--; subpositionIterator._totalDistance += subpositionIterator._getDistanceBetweenSegments(trackPosition, trackDistances, nextTrackPosition, nextTrackDistance); @@ -164,7 +166,7 @@ function calculateSpacingToPrecedingVehicle(car: Car, carInFront: Car): number | } const subposition = car.subposition; - let distances = getTrackDistances(iteratorSegment, subposition, currentTrackLocation.direction); + let distances = getTrackSegmentDistances(iteratorSegment, subposition, currentTrackLocation.direction); let totalDistance = (distances._progressLength - currentProgress); for (let i = 0; i < MaxForwardIterations; i++) { @@ -183,7 +185,7 @@ function calculateSpacingToPrecedingVehicle(car: Car, carInFront: Car): number | return null; } - distances = getTrackDistances(iteratorSegment, subposition, iteratorPosition.direction); + distances = getTrackSegmentDistances(iteratorSegment, subposition, iteratorPosition.direction); totalDistance += distances._progressLength; } return null; @@ -223,8 +225,7 @@ function getIndexForTrackElementAt(coords: CoordsXYZD): number | null const element = tile.elements[i]; if (element.type === "track" && element.baseZ === coords.z - && element.direction === coords.direction - && element.sequence === 0) + && element.direction === coords.direction) { return i; } @@ -304,7 +305,7 @@ class ForwardIterator extends SubpositionIterator for (; distanceIdx < trackPieceLength && remainingProgress > 0; distanceIdx++) { this._totalDistance += distances[distanceIdx]; - Log.debug("total +=", distances[distanceIdx], "=", this._totalDistance, "(step:", distanceIdx, ", remaining:", remainingProgress, ")"); + Log.debug("total +=", distances[distanceIdx], "=", this._totalDistance, "(forward step:", distanceIdx, "/", distances.length, ", remaining:", remainingProgress, ")"); remainingProgress--; } @@ -350,7 +351,7 @@ class BackwardIterator extends SubpositionIterator while (--distanceIdx >= 0 && remainingProgress > 0) { this._totalDistance += distances[distanceIdx]; - Log.debug("total +=", distances[distanceIdx], "=", this._totalDistance, "(step:", distanceIdx, ", remaining:", remainingProgress, ")"); + Log.debug("total +=", distances[distanceIdx], "=", this._totalDistance, "(backward step:", distanceIdx, "/", distances.length, ", remaining:", remainingProgress, ")"); remainingProgress--; } this._remainingProgress = remainingProgress; diff --git a/src/services/subpositionHelper.ts b/src/services/subpositionHelper.ts index 0fd96b0..ac78b48 100644 --- a/src/services/subpositionHelper.ts +++ b/src/services/subpositionHelper.ts @@ -1,3 +1,6 @@ +import { abs } from "../utilities/math"; + + /** * @see https://github.com/OpenRCT2/OpenRCT2/blob/develop/src/openrct2/ride/VehicleData.cpp#L801 */ @@ -20,13 +23,35 @@ const distanceCache: Record = {}; /** * Gets distance information about the current track segment. */ -export function getTrackDistances(track: TrackSegment, subpositionType: number, direction: Direction): Readonly +export function getTrackSegmentDistances(track: TrackSegment, subpositionType: number, direction: Direction): Readonly { const key = createCacheKey(track.type, subpositionType, direction); let value = distanceCache[key]; if (!value) { - value = new TrackDistances(track.getSubpositions(subpositionType, direction)); + value = new TrackDistances(track, subpositionType, direction); + distanceCache[key] = value; + } + return value; +} + + +/** + * Gets distance information about the specified track type. + */ +export function getTrackTypeDistances(trackType: number, subpositionType: number, direction: Direction): Readonly +{ + const key = createCacheKey(trackType, subpositionType, direction); + let value = distanceCache[key]; + if (!value) + { + const segment = context.getTrackSegment(trackType); + if (!segment) + { + throw Error("Unknown track piece"); + } + + value = new TrackDistances(segment, subpositionType, direction); distanceCache[key] = value; } return value; @@ -54,35 +79,99 @@ function createCacheKey(trackType: number, subposition: number, direction: Direc } +/** + * Gets the non-square root distance between two positions. + */ +function distance(start: Readonly, end: Readonly): number +{ + const deltaX = (start.x - end.x); + const deltaY = (start.y - end.y); + const deltaZ = (start.z - end.z); + return abs(deltaX * deltaX + deltaY * deltaY + deltaZ + deltaZ); +} + + +/** + * Rotates the coordinates by 90 degrees a specified amount of times. + */ +function rotate(coordinates: CoordsXY, by: number): void +{ + const rotation = (by & 3); + const x = coordinates.x; + const y = coordinates.y; + + if (rotation == 1) + { + coordinates.x = y; + coordinates.y = -x; + } + else if (rotation == 2) + { + coordinates.x = -x; + coordinates.y = -y; + } + else if (rotation == 3) + { + coordinates.x = -y; + coordinates.y = x; + } +} + + +/** + * A single sequence in a multi-tile track-piece. + */ +interface TrackSequence extends CoordsXYZ +{ + // Progress' values closest to the center of that sequence element. + progress: number | null; +} + + /** * The positional data for a specific track element. */ export class TrackDistances { + // Offsets of the starting subposition. _startX: number; _startY: number; _startZ: number; + // Offsets of the ending subposition. _endX: number; _endY: number; _endZ: number; + // Direction of the track piece. + _direction: Direction; + // Distances between each of the subpositions. _distances: Uint16Array; + // All the sequences in a potentially multi-part track piece. + _sequences: TrackSequence[]; + // Total length of the track segment/piece. _progressLength: number; + // Total distance of all subpositions on the track segment/piece. _totalDistance: number; - constructor(subpositions: TrackSubposition[]) + constructor(track: TrackSegment, subpositionType: number, direction: Direction) { - const length = subpositions.length; + // Note: optimized to reduce external Duktape calls. + const subpositions = track.getSubpositions(subpositionType, direction); + const subpositionsLength = subpositions.length; + const elements = track.elements; + const elementsLength = elements.length; const start = subpositions[0]; - const distances = new Uint16Array(length - 1); + const distances = new Uint16Array(subpositionsLength - 1); + const sequences = new Array(elementsLength); let totalDistance = 0; + let idx = 1; - // Note: optimized to reduce external Duktape calls. let { x, y, z } = start; this._startX = x; this._startY = y; this._startZ = z; - for (let idx = 1; idx < length; idx++) + // Get the distance between each of the subpositions. + for (; idx < subpositionsLength; idx++) { const next = subpositions[idx]; const distance = getSubpositionTranslationDistance(x, y, z, next.x, next.y, next.z); @@ -94,11 +183,60 @@ export class TrackDistances y = next.y; z = next.z; } + + // Check if current subposition is closest to any sequence center. + for (idx = 0; idx < elementsLength; idx++) + { + const element = elements[idx]; + const center = { x: element.x, y: element.y, z: element.z }; + let subposition = 0; + let closestSubposition: number | null = null; + let closestDistance = 10_000; // About max. 3 tiles away (100*100 units, non-square rooted). + let currentDistance; + + rotate(center, direction); + center.x += 16; + center.y += 16; + + for (; subposition < subpositionsLength; subposition++) + { + currentDistance = distance(center, subpositions[subposition]); + if (currentDistance < closestDistance) + { + closestDistance = currentDistance; + closestSubposition = subposition; + } + } + + // Reuse allocated center object for storing the sequence. + center.x -= 16; + center.y -= 16; + center.progress = closestSubposition; + + sequences[idx] = center; + } + this._endX = x; this._endY = y; this._endZ = z; + this._direction = direction; this._distances = distances; + this._sequences = sequences; this._progressLength = distances.length; this._totalDistance = totalDistance; } -} \ No newline at end of file + + /** + * Get the origin of a track piece in world coordinates. + */ + _origin(x: number, y: number, z: number, sequence: number): CoordsXYZD + { + const element = this._sequences[sequence]; + return { + x: x - element.x, + y: y - element.y, + z: z - element.z, + direction: this._direction + }; + } +} diff --git a/src/services/vehicleDragger.ts b/src/services/vehicleDragger.ts index d9e8bf4..c9cd4c3 100644 --- a/src/services/vehicleDragger.ts +++ b/src/services/vehicleDragger.ts @@ -1,14 +1,15 @@ import { Store } from "openrct2-flexui"; import { isMultiplayer } from "../environment"; import { getCarById, RideVehicle } from "../objects/rideVehicle"; +import * as Log from "../utilities/logger"; import { getTileElement } from "../utilities/map"; import { floor } from "../utilities/math"; import { cancelCurrentTool, cancelTools } from "../utilities/tools"; -import { isNull, isUndefined } from "../utilities/type"; +import { isNumber, isUndefined } from "../utilities/type"; import { register } from "./actions"; import { invoke, refreshVehicle } from "./events"; import { getDistanceFromProgress } from "./spacingEditor"; -import { equalCoordsXYZ } from "../utilities/coords"; +import { getTrackTypeDistances } from "./subpositionHelper"; const execute = register("rve-drag-car", updateVehicleDrag); @@ -21,14 +22,8 @@ export const dragToolId = "rve-drag-vehicle"; /** * Enable or disable a tool to drag the vehicle to a new location. */ -export function toggleVehicleDragger(isPressed: boolean, - storeVehicle: Store<[RideVehicle, number] | null>, - storeX: Store, - storeY: Store, - storeZ: Store, - storeTrackLocation: Store, - storeTrackProgress: Store, - onCancel: () => void): void +export function toggleVehicleDragger(isPressed: boolean, storeVehicle: Store<[RideVehicle, number] | null>, storeX: Store, storeY: Store, storeZ: Store, + storeTrackLocation: Store, storeTrackProgress: Store, onCancel: () => void): void { const rideVehicle = storeVehicle.get(); if (!isPressed || !rideVehicle) @@ -44,11 +39,10 @@ export function toggleVehicleDragger(isPressed: boolean, x: storeX.get(), y: storeY.get(), z: storeZ.get(), + track: storeTrackLocation.get(), + progress: storeTrackProgress.get() }; - const originalTrackLocation = storeTrackLocation.get(); - const originalTrackProgress = storeTrackProgress.get(); - let lastPosition: CoordsXYZ = originalPosition; - let lastTrackLocation: CarTrackLocation | null = originalTrackLocation; + let lastPosition: DragPosition = originalPosition; ui.activateTool({ id: dragToolId, @@ -60,37 +54,27 @@ export function toggleVehicleDragger(isPressed: boolean, // Limit updates to 8 fps to avoid bringing down multiplayer servers return; } - const position = getPositionFromTool(args, rideVehicle[0]._type(), rideVehicle[0]._id); - if (position && position.trackLocation && !equalCoordsXYZ(position.trackLocation, lastTrackLocation) || - position && !equalCoordsXYZ(position.tilePosition, lastPosition)) + const vehicle = rideVehicle[0]; + const position = getPositionFromTool(args, vehicle); + if (position && (position.x !== lastPosition.x || position.y !== lastPosition.y || position.z !== lastPosition.z)) { + Log.debug("Selected position:", JSON.stringify(position)); updateCarPosition(rideVehicle, position, DragState.Dragging); - ui.tileSelection.tiles = [{ x: alignWithMap(position.tilePosition.x), y : alignWithMap(position.tilePosition.y) }]; - lastPosition = position.tilePosition; - lastTrackLocation = position.trackLocation; + ui.tileSelection.tiles = [{ x: alignWithMap(position.x), y : alignWithMap(position.y) }]; + lastPosition = position; } }, onDown: () => { originalPosition.revert = false; - const position = { - tilePosition: lastPosition, - trackLocation: lastTrackLocation, - trackProgress: null, - }; - updateCarPosition(rideVehicle, position, DragState.Complete); + updateCarPosition(rideVehicle, lastPosition, DragState.Complete); cancelCurrentTool(); }, onFinish: () => { if (originalPosition.revert) { - const position = { - tilePosition: originalPosition, - trackLocation: originalTrackLocation, - trackProgress: originalTrackProgress, - }; - updateCarPosition(rideVehicle, position, DragState.Cancel); + updateCarPosition(rideVehicle, lastPosition, DragState.Cancel); } ui.tileSelection.tiles = []; onCancel(); @@ -108,11 +92,13 @@ const enum DragState Cancel } -interface DragPosition +/** + * Position currently selected by the drag tool. + */ +interface DragPosition extends CoordsXYZ { - tilePosition: CoordsXYZ, - trackLocation: CarTrackLocation | null, - trackProgress: number | null, + track?: CarTrackLocation | null; + progress?: number | null; } /** @@ -128,13 +114,13 @@ interface DragVehicleArgs /** * Get a possible position to drag the vehicle to. */ -function getPositionFromTool(args: ToolEventArgs, vehicleType: RideObjectVehicle | null, currentId: number): DragPosition | null +function getPositionFromTool(args: ToolEventArgs, vehicle: RideVehicle): DragPosition | null { const { entityId, mapCoords, tileElementIndex } = args; + const result = {}; let x: number | undefined, y: number | undefined, z: number | undefined; - let trackLocation: CarTrackLocation | null = null; - if (!isUndefined(entityId) && entityId !== currentId) + if (!isUndefined(entityId) && entityId !== vehicle._id) { const entity = map.getEntity(entityId); x = entity.x; @@ -143,18 +129,19 @@ function getPositionFromTool(args: ToolEventArgs, vehicleType: RideObjectVehicle } else if (mapCoords && !isUndefined(tileElementIndex)) { - x = (mapCoords.x + 16); - y = (mapCoords.y + 16); + x = mapCoords.x; + y = mapCoords.y; const element = getTileElement(x, y, tileElementIndex); const type = element.type; + const vehicleType = vehicle._type(); const tabHeight = (vehicleType) ? vehicleType.tabHeight : 0; - z = (type === "footpath" || type === "banner" || type === "wall" || type === "track") + z = (type === "footpath" || type === "banner" || type === "wall") ? element.baseZ : element.clearanceZ; - // Custom heights for surface elements if (type === "surface") { + // Adjust height for surface elements slopes and water const waterZ = element.waterHeight; if (waterZ > z) { @@ -165,20 +152,25 @@ function getPositionFromTool(args: ToolEventArgs, vehicleType: RideObjectVehicle z += 8; } } - - if (type === "track") { - const iterator = map.getTrackIterator({x, y}, tileElementIndex); - if (!isNull(iterator)) { - trackLocation = { - x: iterator.position.x-16, // back to block center - y: iterator.position.y-16, - z: iterator.position.z, - direction: element.direction, - trackType: element.trackType - }; + else if (type === "track") + { + // Find correct track offsets for track pieces + const sequence = element.sequence; + if (isNumber(sequence)) + { + const trackType = element.trackType; + const subposition = vehicle._car().subposition; + const distances = getTrackTypeDistances(trackType, subposition, element.direction); + const origin = distances._origin(x, y, element.baseZ, sequence); + + origin.trackType = trackType; + result.track = origin; + result.progress = distances._sequences[sequence].progress; } } + x += 16; + y += 16; // Increase height for certain vehicles like inverted ones based on the height in the tab icon // 29 for actual inverted, inverted tabheight for other negatives, 0 for big cars z += (tabHeight < -10) ? 29 : (tabHeight < 0) ? -tabHeight : 0; @@ -187,7 +179,12 @@ function getPositionFromTool(args: ToolEventArgs, vehicleType: RideObjectVehicle { return null; } - return {tilePosition: { x, y, z }, trackLocation, trackProgress: null}; + + result.x = x; + result.y = y; + result.z = z; + + return result; } /** @@ -210,16 +207,23 @@ function updateVehicleDrag(args: DragVehicleArgs): void return; } - if (isNull(args.position.trackLocation)) { - const position = args.position.tilePosition; + const position = args.position; + const track = position.track; + const progress = position.progress; + if (track && isNumber(progress)) + { + track.x = alignWithMap(track.x); + track.y = alignWithMap(track.y); + + car.trackLocation = track; + Log.debug("Travel to", JSON.stringify(position), "by:", progress, "-", car.trackProgress, "=", progress - car.trackProgress); + car.travelBy(getDistanceFromProgress(car, progress - car.trackProgress)); + } + else + { car.x = position.x; car.y = position.y; car.z = position.z; - } else { - car.trackLocation = args.position.trackLocation; - if (!isNull(args.position.trackProgress) && car.trackProgress != args.position.trackProgress) { - car.travelBy(getDistanceFromProgress(car, args.position.trackProgress - car.trackProgress)); - } } invoke(refreshVehicle, id); diff --git a/src/ui/mainWindow.ts b/src/ui/mainWindow.ts index 79fa318..d134011 100644 --- a/src/ui/mainWindow.ts +++ b/src/ui/mainWindow.ts @@ -120,8 +120,7 @@ export const mainWindow = window({ image: 5174, // SPR_PICKUP_BTN isPressed: twoway(model._isDragging), disabled: model._isPositionDisabled, - onChange: pressed => toggleVehicleDragger(pressed, model._selectedVehicle, model._x, model._y, model._z, - model._trackLocation, model._trackProgress, () => model._isDragging.set(false)) + onChange: pressed => toggleVehicleDragger(pressed, model._selectedVehicle, model._x, model._y, model._z, model._trackLocation, model._trackProgress, () => model._isDragging.set(false)) }), toggle({ width: buttonSize, height: buttonSize, @@ -512,4 +511,4 @@ function positionSpinner(params: LabelledSpinnerParams & FlexiblePosition): Widg params.tooltip = "The fantastic map location of your vehicle and where to find it. Only works when the vehicle is not moving."; params._noDisabledMessage = true; return labelSpinner(params); -} +} \ No newline at end of file diff --git a/src/utilities/coords.ts b/src/utilities/coords.ts deleted file mode 100644 index 1bf284c..0000000 --- a/src/utilities/coords.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { isNull } from "./type"; - -export function equalCoordsXYZ(a: CoordsXYZ, b: CoordsXYZ | null): boolean -{ - if (isNull(b)) { - return false; - } - - return a.x === b.x - && a.y === b.y - && a.z === b.z; -}