From 8748a2187a0c963cb7c116cbe058310c0cd568d2 Mon Sep 17 00:00:00 2001 From: Even Torset Date: Mon, 12 Aug 2024 02:01:21 +0200 Subject: [PATCH 1/9] Added Property.prototype.clamp. --- CHANGELOG.md | 5 +++ src/fxr.ts | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c3b042..fa78b8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [Unreleased] + +### Highlights +- Added a `clamp` method to properties. This slices off peaks and troughs outside of a given range to make sure the property value stays within the range. + ## [14.0.1] - 2024-08-11 ### Highlights diff --git a/src/fxr.ts b/src/fxr.ts index 09e6d5f..dd48ba5 100644 --- a/src/fxr.ts +++ b/src/fxr.ts @@ -6816,8 +6816,8 @@ function uniqueArray(a: T[]) { return Array.from(new Set(a)) } -function lerp(a: number, b: number, c: number) { - return a + (b - a) * c +function lerp(a: number, b: number, t: number) { + return a + (b - a) * t } function interpolateSegments(arr: number[], targetS: number, maxSegments: number): number[] { @@ -7800,6 +7800,100 @@ function scalePropMods(prop: Property, } } +function clampPropValue(v: T, min: T, max: T): T { + if (Array.isArray(v) && Array.isArray(min) && Array.isArray(max)) { + return v.map((e, i) => Math.max(min[i], Math.min(max[i], e))) as T + } else if (!Array.isArray(v) && !Array.isArray(min) && !Array.isArray(max)) { + return Math.max(min, Math.min(max, v)) as T + } else { + throw new Error('Invalid property type inputs.') + } +} + +function lerpPropValue(v1: T, v2: T, t: number): T { + if (isVector(v1) && isVector(v2)) { + return v1.map((e, i) => lerp(e, v2[i], t)) as T + } else if (typeof v1 === 'number' && typeof v2 === 'number') { + return lerp(v1, v2, t) as T + } else { + throw new Error('') + } +} + +function findIntersection( + kf1: Keyframe, + kf2: Keyframe, + min: TypeMap.PropertyValue[T], + max: TypeMap.PropertyValue[T] +): Keyframe | null { + for (let i = 0; i < 4; i++) { + if ((kf1.value[i] < min[i] && kf2.value[i] > min[i]) || (kf1.value[i] > max[i] && kf2.value[i] < max[i])) { + const t = (kf1.value[i] < min[i] ? min[i] : max[i] - kf1.value[i]) / (kf2.value[i] - kf1.value[i]) + const position = kf1.position + t * (kf2.position - kf1.position) + const value = lerpPropValue(kf1.value, kf2.value, t) + return new Keyframe(position, value) + } + } + return null +} + +function clampKeyframes( + keyframes: Keyframe[], + min: TypeMap.PropertyValue[T], + max: TypeMap.PropertyValue[T] +): Keyframe[] { + const clampedKeyframes: Keyframe[] = [] + for (let i = 0; i < keyframes.length - 1; i++) { + const kf1 = keyframes[i] + const kf2 = keyframes[i + 1] + clampedKeyframes.push(new Keyframe(kf1.position, clampPropValue(kf1.value, min, max))) + const intersection = findIntersection(kf1, kf2, min, max) + if (intersection) { + clampedKeyframes.push(intersection) + } + } + const lastKeyframe = keyframes[keyframes.length - 1] + clampedKeyframes.push(new Keyframe(lastKeyframe.position, clampPropValue(lastKeyframe.value, min, max))) + return clampedKeyframes +} + +function clampProp( + prop: Property, + min: TypeMap.PropertyValue[T], + max: TypeMap.PropertyValue[T] +): Property { + let clone = prop.clone().minify() + if (clone instanceof ValueProperty) { + clone.value = clampPropValue(clone.value, min, max) + } else if (clone instanceof SequenceProperty || clone instanceof ComponentSequenceProperty) { + if (clone instanceof ComponentSequenceProperty) { + clone = clone.combineComponents() + } + let seq = clone as SequenceProperty + if (seq.function !== PropertyFunction.Stepped && seq.function !== PropertyFunction.Linear) { + const posSet = new Set() + for (const keyframe of seq.keyframes) { + posSet.add(keyframe.position) + } + const positions = filterMillisecondDiffs(posSet).sort((a, b) => a - b) + seq = new LinearProperty( + seq.loop, + filterMillisecondDiffs(interpolateSegments(positions, 0.1, 40)) + .map(e => new Keyframe(e, seq.valueAt(e))) + ) + } + if (seq.function === PropertyFunction.Stepped) { + for (const kf of seq.keyframes) { + kf.value = clampPropValue(kf.value, min, max) + } + } else { + seq.keyframes = clampKeyframes(seq.keyframes, min, max) + } + clone = seq + } + return clone.minify() +} + const ActionDataConversion = { [ActionType.StaticNodeTransform]: { read(props: StaticNodeTransformParams, game: Game) { @@ -39005,6 +39099,10 @@ abstract class Property impleme return this } + clamp(min: TypeMap.PropertyValue[T], max: TypeMap.PropertyValue[T]): Property { + return clampProp(this, min, max) + } + abstract fieldCount: number abstract fields: NumericalField[] abstract toJSON(): any From e434d4591c39f173ee69dd319b637358c614b54f Mon Sep 17 00:00:00 2001 From: Even Torset Date: Mon, 12 Aug 2024 03:30:12 +0200 Subject: [PATCH 2/9] Minify improvements. --- CHANGELOG.md | 3 ++ src/fxr.ts | 79 ++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa78b8d..306ad5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Highlights - Added a `clamp` method to properties. This slices off peaks and troughs outside of a given range to make sure the property value stays within the range. +- Added a `minify` method to modifiers. This does nothing for most of the modifier types, but for the external value modifiers this will minify the `factor` property. +- The `minify` method on properties now also minify the modifiers. +- The `minify` method on Stepped and Linear properties now removes keyframes that aren't doing anything. For example, if you have a linear scalar property with these keyframe values: `0, 1, 1, 1, 0`, then the middle keyframe is not doing anything, because it has two equivalent ones surrounding it, so when minified, this property would end up having these keyframe values: `0, 1, 1, 0`. ## [14.0.1] - 2024-08-11 diff --git a/src/fxr.ts b/src/fxr.ts index dd48ba5..2a4b5c7 100644 --- a/src/fxr.ts +++ b/src/fxr.ts @@ -1739,6 +1739,7 @@ export interface IModifier { toJSON(): any clone(): IModifier separateComponents(): IModifier[] + minify(): IModifier } export interface ActionMeta { @@ -7894,6 +7895,15 @@ function clampProp( return clone.minify() } +function propValueEqual(a: PropertyValue, b: PropertyValue) { + if (isVector(a) && isVector(b)) { + return a.every((e, i) => e === b[i]) + } else if (!isVector(a) && !isVector(b)) { + return a === b + } + return false +} + const ActionDataConversion = { [ActionType.StaticNodeTransform]: { read(props: StaticNodeTransformParams, game: Game) { @@ -38977,17 +38987,9 @@ class Keyframe implements IBasicKeyframe { } static equal | IBezierKeyframe>(kf1: K, kf2: K) { - return ( - Array.isArray(kf1.value) && kf1.value.every((e, i) => e === kf2.value[i]) || - kf1.value === kf2.value - ) && ( - !('p1' in kf1 && 'p1' in kf2) || ( - Array.isArray(kf1.p1) && kf1.p1.every((e, i) => e === 0 && kf2.p1[i] === 0) || - kf1.p1 === 0 && kf2.p1 === 0 - ) && ( - Array.isArray(kf1.p2) && kf1.p2.every((e, i) => e === 0 && kf2.p2[i] === 0) || - kf1.p2 === 0 && kf2.p2 === 0 - ) + return propValueEqual(kf1.value, kf2.value) && ( + !('p1' in kf1 && 'p1' in kf2) || + propValueEqual(kf1.p1, kf2.p1) && propValueEqual(kf1.p2, kf2.p2) ) } @@ -39257,7 +39259,7 @@ class ValueProperty minify(): ValueProperty { const clone = this.clone() - clone.modifiers = clone.modifiers.filter(Modifier.isEffective) + clone.modifiers = clone.modifiers.map(mod => mod.minify()).filter(Modifier.isEffective) return clone } @@ -39580,19 +39582,26 @@ class SequenceProperty } minify(): Property { + const mods = this.modifiers.map(mod => mod.minify()).filter(Modifier.isEffective) if (this.keyframes.length === 1 || this.keyframes.slice(1).every(kf => Keyframe.equal(this.keyframes[0], kf))) { if (this.valueType === ValueType.Scalar) { - return new ConstantProperty(this.keyframes[0].value as number).withModifiers( - ...this.modifiers.filter(Modifier.isEffective) - ) + return new ConstantProperty(this.keyframes[0].value as number).withModifiers(...mods) } else { - return new ConstantProperty(...(this.keyframes[0].value as Vector)).withModifiers( - ...this.modifiers.filter(Modifier.isEffective) - ) + return new ConstantProperty(...(this.keyframes[0].value as Vector)).withModifiers(...mods) } } const clone = this.clone() - clone.modifiers = clone.modifiers.filter(Modifier.isEffective) + clone.modifiers = mods + if (clone.function === PropertyFunction.Stepped) { + clone.keyframes = clone.keyframes.filter((e, i, a) => i === 0 || i === a.length - 1 || !propValueEqual(a[i-1].value, e.value)) + } else if (clone.function === PropertyFunction.Linear) { + clone.keyframes = clone.keyframes.filter((e, i, a) => + i === 0 || + i === a.length - 1 || + !propValueEqual(a[i-1].value, e.value) || + !propValueEqual(e.value, a[i+1].value) + ) + } return clone } @@ -39837,12 +39846,12 @@ class ComponentSequenceProperty return new ConstantProperty( ...this.components.map(c => c.keyframes[0].value) ).withModifiers( - ...this.modifiers.filter(Modifier.isEffective) + ...this.modifiers.map(mod => mod.minify()).filter(Modifier.isEffective) ) } if (this.canBeSimplified()) return this.combineComponents().minify() const clone = this.clone() - clone.modifiers = clone.modifiers.filter(Modifier.isEffective) + clone.modifiers = clone.modifiers.map(mod => mod.minify()).filter(Modifier.isEffective) return clone } @@ -40350,6 +40359,10 @@ class GenericModifier implements IModifier { throw new Error('Generic modifiers cannot be split into component modifiers.') } + minify(): GenericModifier { + return this + } + } /** @@ -40427,6 +40440,10 @@ class RandomDeltaModifier implements IModifier { } } + minify(): RandomDeltaModifier { + return this + } + } /** @@ -40506,6 +40523,10 @@ class RandomRangeModifier implements IModifier { } } + minify(): RandomRangeModifier { + return this + } + } /** @@ -40582,6 +40603,10 @@ class RandomFractionModifier implements IModifier { return (this.max as Vector).map((e, i) => new RandomFractionModifier(e, (this.seed as Vector)[i])) } + minify(): RandomFractionModifier { + return this + } + } /** @@ -40642,6 +40667,12 @@ class ExternalValue1Modifier implements IModifier { return this.factor.separateComponents().map(e => new ExternalValue1Modifier(this.externalValue, e)) } + minify(): ExternalValue1Modifier { + const clone = this.clone() + clone.factor = clone.factor.minify() as TypeMap.Property[T] + return clone + } + } class ExternalValue2Modifier implements IModifier { @@ -40698,6 +40729,12 @@ class ExternalValue2Modifier implements IModifier { return this.factor.separateComponents().map(e => new ExternalValue2Modifier(this.externalValue, e)) } + minify(): ExternalValue2Modifier { + const clone = this.clone() + clone.factor = clone.factor.minify() as TypeMap.Property[T] + return clone + } + } /** From a766e860c0e7306e2671282c91daa8934d4ee876 Mon Sep 17 00:00:00 2001 From: Even Torset Date: Mon, 12 Aug 2024 05:57:37 +0200 Subject: [PATCH 3/9] Fixed some problems with the previous two commits. --- src/fxr.ts | 79 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/src/fxr.ts b/src/fxr.ts index 2a4b5c7..d737837 100644 --- a/src/fxr.ts +++ b/src/fxr.ts @@ -7821,21 +7821,54 @@ function lerpPropValue(v1: T, v2: T, t: number): T { } } -function findIntersection( - kf1: Keyframe, - kf2: Keyframe, - min: TypeMap.PropertyValue[T], - max: TypeMap.PropertyValue[T] -): Keyframe | null { - for (let i = 0; i < 4; i++) { - if ((kf1.value[i] < min[i] && kf2.value[i] > min[i]) || (kf1.value[i] > max[i] && kf2.value[i] < max[i])) { - const t = (kf1.value[i] < min[i] ? min[i] : max[i] - kf1.value[i]) / (kf2.value[i] - kf1.value[i]) - const position = kf1.position + t * (kf2.position - kf1.position) - const value = lerpPropValue(kf1.value, kf2.value, t) - return new Keyframe(position, value) +function findIntersections( + keyframe1: Keyframe, + keyframe2: Keyframe, + minValue: TypeMap.PropertyValue[T], + maxValue: TypeMap.PropertyValue[T] +): Keyframe[] { + const { position: x1, value: y1 } = keyframe1 + const { position: x2, value: y2 } = keyframe2 + + if (y1 === y2) { + return [] + } + + function interpolate(y1: number, y2: number, minValue: number, maxValue: number) { + const slope = (y2 - y1) / (x2 - x1) + const positions: number[] = [] + + const tMin = (minValue - y1) / slope + if (tMin > 0 && tMin < (x2 - x1)) { + const xMin = x1 + tMin + if (xMin >= x1 && xMin <= x2) { + positions.push(xMin) + } + } + + const tMax = (maxValue - y1) / slope + if (tMax > 0 && tMax < (x2 - x1)) { + const xMax = x1 + tMax + if (xMax >= x1 && xMax <= x2) { + positions.push(xMax) + } + } + + return positions + } + + const results: Keyframe[] = [] + if (typeof y1 === 'number' && typeof y2 === 'number') { + const positions = interpolate(y1, y2, minValue as number, maxValue as number) + results.push(...positions.map(x => new Keyframe(x, clampPropValue(lerpPropValue(y1, y2, (x - x1) / (x2 - x1)), minValue, maxValue)))) + } else if (Array.isArray(y1) && Array.isArray(y2)) { + const comps = y1.length + for (let c = 0; c < comps; c++) { + const positions = interpolate(y1[c], y2[c], minValue[c], maxValue[c]) + results.push(...positions.map(x => new Keyframe(x, clampPropValue(lerpPropValue(y1, y2, (x - x1) / (x2 - x1)), minValue, maxValue)))) } } - return null + return results } function clampKeyframes( @@ -7846,12 +7879,10 @@ function clampKeyframes( const clampedKeyframes: Keyframe[] = [] for (let i = 0; i < keyframes.length - 1; i++) { const kf1 = keyframes[i] - const kf2 = keyframes[i + 1] - clampedKeyframes.push(new Keyframe(kf1.position, clampPropValue(kf1.value, min, max))) - const intersection = findIntersection(kf1, kf2, min, max) - if (intersection) { - clampedKeyframes.push(intersection) - } + clampedKeyframes.push( + new Keyframe(kf1.position, clampPropValue(kf1.value, min, max)), + ...findIntersections(kf1, keyframes[i + 1], min, max) + ) } const lastKeyframe = keyframes[keyframes.length - 1] clampedKeyframes.push(new Keyframe(lastKeyframe.position, clampPropValue(lastKeyframe.value, min, max))) @@ -7890,16 +7921,22 @@ function clampProp( } else { seq.keyframes = clampKeyframes(seq.keyframes, min, max) } + seq.sortKeyframes() clone = seq } return clone.minify() } +const FLOAT32_EPSILON = 2 ** -23 +function f32Equal(a: number, b: number) { + return Math.abs(a - b) <= FLOAT32_EPSILON +} + function propValueEqual(a: PropertyValue, b: PropertyValue) { if (isVector(a) && isVector(b)) { - return a.every((e, i) => e === b[i]) + return a.every((e, i) => f32Equal(e, b[i])) } else if (!isVector(a) && !isVector(b)) { - return a === b + return f32Equal(a, b) } return false } From 2ea1d10da950df3724564438c548957cf7c99e03 Mon Sep 17 00:00:00 2001 From: Even Torset Date: Tue, 13 Aug 2024 02:11:11 +0200 Subject: [PATCH 4/9] Changed target branch for dependabot. --- .github/dependabot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index aff82a1..0277eb8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,5 +2,6 @@ version: 2 updates: - package-ecosystem: "npm" directory: "/" + target-branch: "dev" schedule: interval: "weekly" From 96e7ff68a968790772fa2da0058ac0c68b5e5760 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 02:29:35 +0200 Subject: [PATCH 5/9] Bump @types/node from 22.1.0 to 22.2.0 (#49) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.1.0 to 22.2.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: Even Torset Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff99aa2..e9a8e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,9 +24,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", - "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz", + "integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==", "dev": true, "dependencies": { "undici-types": "~6.13.0" From 08199d07f8bd44d6202c47c93afd21754c2f1a00 Mon Sep 17 00:00:00 2001 From: Even Torset Date: Tue, 13 Aug 2024 12:15:59 +0200 Subject: [PATCH 6/9] Added FXR.prototype.find. --- CHANGELOG.md | 1 + src/fxr.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 306ad5b..a965a68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Highlights +- Added a `find` method to FXR objects, which finds and returns a value at a given path in the FXR. For example, a path like `root/nodes/0` would be the first child node of the root node. - Added a `clamp` method to properties. This slices off peaks and troughs outside of a given range to make sure the property value stays within the range. - Added a `minify` method to modifiers. This does nothing for most of the modifier types, but for the external value modifiers this will minify the `factor` property. - The `minify` method on properties now also minify the modifiers. diff --git a/src/fxr.ts b/src/fxr.ts index d737837..fc0a647 100644 --- a/src/fxr.ts +++ b/src/fxr.ts @@ -9261,6 +9261,36 @@ class FXR { return `f${this.id.toString().padStart(9, '0')}.fxr` } + /** + * Finds and returns a value at a given path. If the path does not match + * anything, this returns `null`. + * + * For example, to get the appearance action in the second effect in the + * first child node of the root node, you would use this path: + * ```js + * fxr.find(['root', 'nodes', 0, 'effects', 1, 'appearance']) + * // Or in string form: + * fxr.find('root/nodes/0/effects/1/appearance') + * // Both are equivalent to this: + * fxr.root.nodes[0].effects[1].appearance + * ``` + * @param path The path to the value to look for. + */ + find(path: (string | number)[] | string): any { + if (typeof path === 'string') { + path = path.replace(/^[\/\s]+|[\/\s]+$/g, '').split('/') + } + let current: any = this + for (const key of path) { + if (current && current[key] !== undefined) { + current = current[key] + } else { + return null + } + } + return current + } + } //#region State From d753951c4a52fd305e23314781449f715f03f77f Mon Sep 17 00:00:00 2001 From: Even Torset Date: Wed, 14 Aug 2024 03:42:10 +0200 Subject: [PATCH 7/9] Added NodeWithEffects.prototype.getActiveEffect. --- CHANGELOG.md | 4 +++- src/fxr.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a965a68..92e7b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Changelog -## [Unreleased] +## [14.1.0] - 2024-08-14 ### Highlights - Added a `find` method to FXR objects, which finds and returns a value at a given path in the FXR. For example, a path like `root/nodes/0` would be the first child node of the root node. +- Added a `getActiveEffect` method to nodes that contain effects, which returns the effect that would be active when a given state index is active. - Added a `clamp` method to properties. This slices off peaks and troughs outside of a given range to make sure the property value stays within the range. - Added a `minify` method to modifiers. This does nothing for most of the modifier types, but for the external value modifiers this will minify the `factor` property. - The `minify` method on properties now also minify the modifiers. @@ -150,6 +151,7 @@ - External values 2000 and 70200 for AC6 have been documented thanks to lugia19. - Fixed action 301 (EqualDistanceEmitter) missing a type for one of its fields, potentially causing issues when writing to DS3's structure. +[14.1.0]: https://github.com/EvenTorset/fxr/compare/v14.0.1...v14.1.0 [14.0.1]: https://github.com/EvenTorset/fxr/compare/v14.0.0...v14.0.1 [14.0.0]: https://github.com/EvenTorset/fxr/compare/v13.0.0...v14.0.0 [13.0.0]: https://github.com/EvenTorset/fxr/compare/v12.2.0...v13.0.0 diff --git a/src/fxr.ts b/src/fxr.ts index fc0a647..ad9e0c4 100644 --- a/src/fxr.ts +++ b/src/fxr.ts @@ -10415,6 +10415,16 @@ abstract class NodeWithEffects extends Node { return this } + /** + * Returns the effect that is active when a given {@link State state} index + * is active. If no effects are active for the state, this returns `null` + * instead. + * @param stateIndex The index of a {@link FXR.states state in the FXR}. + */ + getActiveEffect(stateIndex: number): IEffect | null { + return this.effects[this.stateEffectMap[stateIndex] ?? this.stateEffectMap[0]] ?? null + } + } /** From c90202f1d703f828d6af26130cd44a7b040a2123 Mon Sep 17 00:00:00 2001 From: Even Torset Date: Wed, 14 Aug 2024 03:45:53 +0200 Subject: [PATCH 8/9] Added proper documentation for FXRUtility.transform. --- src/fxr.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/fxr.ts b/src/fxr.ts index ad9e0c4..84a0f39 100644 --- a/src/fxr.ts +++ b/src/fxr.ts @@ -41864,10 +41864,19 @@ namespace FXRUtility { return rad * 180 / Math.PI } - export function transform(center: Vector3, axis: Vector3, roll: number, nodes: Node[]) { + /** + * Wraps the given {@link nodes} in a node or set of nodes that have a + * transform applied to them. This can be useful to make the nodes point in a + * specific direction. + * @param offset Translation offset. + * @param axis The direction to point towards. + * @param roll The roll angle in degrees. + * @param nodes The nodes to transform. + */ + export function transform(offset: Vector3, axis: Vector3, roll: number, nodes: Node[]) { if (axis[0] === 0 && axis[1] === 0 && axis[2] === 0 && roll === 0) { return new BasicNode([ - NodeTransform({ offset: center }) + NodeTransform({ offset }) ], nodes) } axis = normalizeVector3(axis) @@ -41875,7 +41884,7 @@ namespace FXRUtility { const pitch = -Math.asin(axis[0]) * 180 / Math.PI return new BasicNode([ NodeTransform({ - offset: center, + offset, rotation: [yaw, 0, 0] }) ], [ From e359e074fe17b6934cf68e4cfc2e992d8ff41dbb Mon Sep 17 00:00:00 2001 From: Even Torset Date: Wed, 14 Aug 2024 03:46:15 +0200 Subject: [PATCH 9/9] 14.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9a8e86..0038379 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cccode/fxr", - "version": "14.0.1", + "version": "14.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cccode/fxr", - "version": "14.0.1", + "version": "14.1.0", "license": "Unlicense", "devDependencies": { "@types/node": "^22.0.0", diff --git a/package.json b/package.json index 784868c..cd6c52d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cccode/fxr", - "version": "14.0.1", + "version": "14.1.0", "description": "JavaScript library for creating and editing FXR files for Dark Souls 3, Sekiro, Elden Ring, and Armored Core 6.", "author": "CCCode", "type": "module",