-module(viva_math@vector). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/viva_math/vector.gleam"). -export([zero/0, splat/1, from_list/1, to_list/1, add/2, sub/2, scale/2, 'div'/2, negate/1, multiply/2, dot/2, cross/2, length_squared/1, length/1, distance/2, distance_squared/2, normalize/1, lerp/3, clamp/3, clamp_pad/1, map/2, is_close/3, weighted_average/2, min/2, max/2, sum/1, average/1, pad/3, pleasure/1, arousal/1, dominance/1]). -export_type([vec3/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Vector operations for 3D space (PAD model).\n" "\n" " Vec3 is the fundamental type for emotional state in VIVA:\n" " - Pleasure (x): [-1, 1] - sadness ↔ joy\n" " - Arousal (y): [-1, 1] - calm ↔ excitement\n" " - Dominance (z): [-1, 1] - submission ↔ control\n" ). -type vec3() :: {vec3, float(), float(), float()}. -file("src/viva_math/vector.gleam", 18). ?DOC(" Create a zero vector.\n"). -spec zero() -> vec3(). zero() -> {vec3, +0.0, +0.0, +0.0}. -file("src/viva_math/vector.gleam", 23). ?DOC(" Create a vector with all components set to the same value.\n"). -spec splat(float()) -> vec3(). splat(Value) -> {vec3, Value, Value, Value}. -file("src/viva_math/vector.gleam", 29). ?DOC( " Create a Vec3 from a list of 3 floats.\n" " Returns Error if list doesn't have exactly 3 elements.\n" ). -spec from_list(list(float())) -> {ok, vec3()} | {error, nil}. from_list(Values) -> case Values of [X, Y, Z] -> {ok, {vec3, X, Y, Z}}; _ -> {error, nil} end. -file("src/viva_math/vector.gleam", 37). ?DOC(" Convert Vec3 to a list of floats.\n"). -spec to_list(vec3()) -> list(float()). to_list(V) -> [erlang:element(2, V), erlang:element(3, V), erlang:element(4, V)]. -file("src/viva_math/vector.gleam", 42). ?DOC(" Add two vectors.\n"). -spec add(vec3(), vec3()) -> vec3(). add(A, B) -> {vec3, erlang:element(2, A) + erlang:element(2, B), erlang:element(3, A) + erlang:element(3, B), erlang:element(4, A) + erlang:element(4, B)}. -file("src/viva_math/vector.gleam", 47). ?DOC(" Subtract vector b from vector a.\n"). -spec sub(vec3(), vec3()) -> vec3(). sub(A, B) -> {vec3, erlang:element(2, A) - erlang:element(2, B), erlang:element(3, A) - erlang:element(3, B), erlang:element(4, A) - erlang:element(4, B)}. -file("src/viva_math/vector.gleam", 52). ?DOC(" Multiply vector by scalar.\n"). -spec scale(vec3(), float()) -> vec3(). scale(V, S) -> {vec3, erlang:element(2, V) * S, erlang:element(3, V) * S, erlang:element(4, V) * S}. -file("src/viva_math/vector.gleam", 57). ?DOC(" Divide vector by scalar. Returns Error if scalar is zero.\n"). -spec 'div'(vec3(), float()) -> {ok, vec3()} | {error, nil}. 'div'(V, S) -> case S =:= +0.0 of true -> {error, nil}; false -> {ok, {vec3, case S of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> erlang:element(2, V) / Gleam@denominator end, case S of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@1 -> erlang:element(3, V) / Gleam@denominator@1 end, case S of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@2 -> erlang:element(4, V) / Gleam@denominator@2 end}} end. -file("src/viva_math/vector.gleam", 65). ?DOC(" Negate a vector.\n"). -spec negate(vec3()) -> vec3(). negate(V) -> {vec3, +0.0 - erlang:element(2, V), +0.0 - erlang:element(3, V), +0.0 - erlang:element(4, V)}. -file("src/viva_math/vector.gleam", 70). ?DOC(" Component-wise multiplication (Hadamard product).\n"). -spec multiply(vec3(), vec3()) -> vec3(). multiply(A, B) -> {vec3, erlang:element(2, A) * erlang:element(2, B), erlang:element(3, A) * erlang:element(3, B), erlang:element(4, A) * erlang:element(4, B)}. -file("src/viva_math/vector.gleam", 75). ?DOC(" Dot product of two vectors.\n"). -spec dot(vec3(), vec3()) -> float(). dot(A, B) -> ((erlang:element(2, A) * erlang:element(2, B)) + (erlang:element(3, A) * erlang:element( 3, B ))) + (erlang:element(4, A) * erlang:element(4, B)). -file("src/viva_math/vector.gleam", 80). ?DOC(" Cross product of two vectors.\n"). -spec cross(vec3(), vec3()) -> vec3(). cross(A, B) -> {vec3, (erlang:element(3, A) * erlang:element(4, B)) - (erlang:element(4, A) * erlang:element( 3, B )), (erlang:element(4, A) * erlang:element(2, B)) - (erlang:element(2, A) * erlang:element( 4, B )), (erlang:element(2, A) * erlang:element(3, B)) - (erlang:element(3, A) * erlang:element( 2, B ))}. -file("src/viva_math/vector.gleam", 89). ?DOC(" Squared length of a vector (avoids sqrt for comparisons).\n"). -spec length_squared(vec3()) -> float(). length_squared(V) -> dot(V, V). -file("src/viva_math/vector.gleam", 94). ?DOC(" Length (magnitude) of a vector.\n"). -spec length(vec3()) -> float(). length(V) -> Squared = length_squared(V), case gleam@float:square_root(Squared) of {ok, Result} -> Result; {error, _} -> +0.0 end. -file("src/viva_math/vector.gleam", 103). ?DOC(" Euclidean distance between two vectors.\n"). -spec distance(vec3(), vec3()) -> float(). distance(A, B) -> length(sub(A, B)). -file("src/viva_math/vector.gleam", 108). ?DOC(" Squared distance between two vectors (avoids sqrt).\n"). -spec distance_squared(vec3(), vec3()) -> float(). distance_squared(A, B) -> length_squared(sub(A, B)). -file("src/viva_math/vector.gleam", 114). ?DOC( " Normalize a vector to unit length.\n" " Returns zero vector if input has zero length.\n" ). -spec normalize(vec3()) -> vec3(). normalize(V) -> Len = length(V), case Len =:= +0.0 of true -> zero(); false -> {vec3, case Len of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> erlang:element(2, V) / Gleam@denominator end, case Len of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@1 -> erlang:element(3, V) / Gleam@denominator@1 end, case Len of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator@2 -> erlang:element(4, V) / Gleam@denominator@2 end} end. -file("src/viva_math/vector.gleam", 123). ?DOC(" Linear interpolation between two vectors.\n"). -spec lerp(vec3(), vec3(), float()) -> vec3(). lerp(A, B, T) -> {vec3, viva_math@common:lerp(erlang:element(2, A), erlang:element(2, B), T), viva_math@common:lerp(erlang:element(3, A), erlang:element(3, B), T), viva_math@common:lerp(erlang:element(4, A), erlang:element(4, B), T)}. -file("src/viva_math/vector.gleam", 132). ?DOC(" Clamp each component to [min, max] range.\n"). -spec clamp(vec3(), float(), float()) -> vec3(). clamp(V, Min, Max) -> {vec3, viva_math@common:clamp(erlang:element(2, V), Min, Max), viva_math@common:clamp(erlang:element(3, V), Min, Max), viva_math@common:clamp(erlang:element(4, V), Min, Max)}. -file("src/viva_math/vector.gleam", 141). ?DOC(" Clamp vector to PAD range [-1, 1] for all components.\n"). -spec clamp_pad(vec3()) -> vec3(). clamp_pad(V) -> clamp(V, -1.0, 1.0). -file("src/viva_math/vector.gleam", 146). ?DOC(" Apply a function to each component.\n"). -spec map(vec3(), fun((float()) -> float())) -> vec3(). map(V, F) -> {vec3, F(erlang:element(2, V)), F(erlang:element(3, V)), F(erlang:element(4, V))}. -file("src/viva_math/vector.gleam", 151). ?DOC(" Check if two vectors are approximately equal.\n"). -spec is_close(vec3(), vec3(), float()) -> boolean(). is_close(A, B, Tolerance) -> Dx = gleam@float:absolute_value(erlang:element(2, A) - erlang:element(2, B)), Dy = gleam@float:absolute_value(erlang:element(3, A) - erlang:element(3, B)), Dz = gleam@float:absolute_value(erlang:element(4, A) - erlang:element(4, B)), ((Dx =< Tolerance) andalso (Dy =< Tolerance)) andalso (Dz =< Tolerance). -file("src/viva_math/vector.gleam", 160). ?DOC( " Compute weighted average of vectors.\n" " weights and vectors must have same length.\n" ). -spec weighted_average(list(vec3()), list(float())) -> {ok, vec3()} | {error, nil}. weighted_average(Vectors, Weights) -> case erlang:length(Vectors) =:= erlang:length(Weights) of false -> {error, nil}; true -> Sum_weights = gleam@list:fold( Weights, +0.0, fun(Acc, W) -> Acc + W end ), case Sum_weights =:= +0.0 of true -> {error, nil}; false -> Weighted = begin _pipe = gleam@list:zip(Vectors, Weights), _pipe@1 = gleam@list:map( _pipe, fun(Pair) -> {V, W@1} = Pair, scale(V, W@1) end ), gleam@list:fold(_pipe@1, zero(), fun add/2) end, {ok, scale(Weighted, case Sum_weights of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> 1.0 / Gleam@denominator end)} end end. -file("src/viva_math/vector.gleam", 186). ?DOC(" Component-wise minimum of two vectors.\n"). -spec min(vec3(), vec3()) -> vec3(). min(A, B) -> {vec3, gleam@float:min(erlang:element(2, A), erlang:element(2, B)), gleam@float:min(erlang:element(3, A), erlang:element(3, B)), gleam@float:min(erlang:element(4, A), erlang:element(4, B))}. -file("src/viva_math/vector.gleam", 191). ?DOC(" Component-wise maximum of two vectors.\n"). -spec max(vec3(), vec3()) -> vec3(). max(A, B) -> {vec3, gleam@float:max(erlang:element(2, A), erlang:element(2, B)), gleam@float:max(erlang:element(3, A), erlang:element(3, B)), gleam@float:max(erlang:element(4, A), erlang:element(4, B))}. -file("src/viva_math/vector.gleam", 196). ?DOC(" Sum of all components.\n"). -spec sum(vec3()) -> float(). sum(V) -> (erlang:element(2, V) + erlang:element(3, V)) + erlang:element(4, V). -file("src/viva_math/vector.gleam", 201). ?DOC(" Average of all components.\n"). -spec average(vec3()) -> float(). average(V) -> sum(V) / 3.0. -file("src/viva_math/vector.gleam", 208). ?DOC(" Create a PAD vector (Pleasure, Arousal, Dominance).\n"). -spec pad(float(), float(), float()) -> vec3(). pad(Pleasure, Arousal, Dominance) -> _pipe = {vec3, Pleasure, Arousal, Dominance}, clamp_pad(_pipe). -file("src/viva_math/vector.gleam", 213). ?DOC(" Get Pleasure component (x).\n"). -spec pleasure(vec3()) -> float(). pleasure(V) -> erlang:element(2, V). -file("src/viva_math/vector.gleam", 218). ?DOC(" Get Arousal component (y).\n"). -spec arousal(vec3()) -> float(). arousal(V) -> erlang:element(3, V). -file("src/viva_math/vector.gleam", 223). ?DOC(" Get Dominance component (z).\n"). -spec dominance(vec3()) -> float(). dominance(V) -> erlang:element(4, V).