diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index 958540e24a..c5c5314fe7 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -3149,6 +3149,110 @@ bool try_to_rewrite_vector_inline_ctor(const Env& env, return false; } +struct Jak1MatrixRowDeref { + RegisterAccess base; + std::vector matrix_tokens; +}; + +std::optional identity_var(Form* form) { + auto* elt = form ? form->try_as_element() : nullptr; + if (!elt || elt->expr().kind() != SimpleExpression::Kind::IDENTITY || + !elt->expr().get_arg(0).is_var()) { + return {}; + } + return elt->expr().get_arg(0).var(); +} + +std::optional deref_result_type(RegisterAccess base, + const std::vector& tokens, + const Env& env) { + TypeSpec current = env.get_variable_type(base, true); + for (const auto& token : tokens) { + if (token.kind() == DerefToken::Kind::FIELD_NAME) { + try { + current = env.dts->ts.lookup_field_info(current.base_type(), token.field_name()).type; + } catch (const std::exception&) { + return {}; + } + } else if (token.kind() == DerefToken::Kind::INTEGER_CONSTANT || + token.kind() == DerefToken::Kind::INTEGER_EXPRESSION) { + if (!current.has_single_arg()) { + return {}; + } + // get_single_arg() refers into current, so copy it before assignment destroys the argument + // storage. + const auto element_type = current.get_single_arg(); + current = element_type; + } else { + return {}; + } + } + return current; +} + +std::optional match_jak1_matrix_row(Form* form, int row, const Env& env) { + auto* deref = form ? form->try_as_element() : nullptr; + if (!deref || deref->is_addr_of() || deref->tokens().size() < 3) { + return {}; + } + + const auto suffix = deref->tokens().size() - 3; + if (!deref->tokens().at(suffix).is_field_name("vector") || + !deref->tokens().at(suffix + 1).is_int(row) || + !deref->tokens().at(suffix + 2).is_field_name("quad")) { + return {}; + } + + auto base = identity_var(deref->base()); + if (!base) { + return {}; + } + std::vector matrix_tokens(deref->tokens().begin(), deref->tokens().begin() + suffix); + auto matrix_type = deref_result_type(*base, matrix_tokens, env); + if (!matrix_type || *matrix_type != TypeSpec("matrix")) { + return {}; + } + return Jak1MatrixRowDeref{*base, std::move(matrix_tokens)}; +} + +bool same_deref_tokens(std::vector lhs, std::vector rhs, const Env& env) { + if (lhs.size() != rhs.size()) { + return false; + } + for (size_t i = 0; i < lhs.size(); ++i) { + if (lhs.at(i).kind() != rhs.at(i).kind()) { + return false; + } + switch (lhs.at(i).kind()) { + case DerefToken::Kind::FIELD_NAME: + if (lhs.at(i).field_name() != rhs.at(i).field_name()) { + return false; + } + break; + case DerefToken::Kind::INTEGER_CONSTANT: + if (lhs.at(i).int_constant() != rhs.at(i).int_constant()) { + return false; + } + break; + case DerefToken::Kind::INTEGER_EXPRESSION: + if (lhs.at(i).expr()->to_string(env) != rhs.at(i).expr()->to_string(env)) { + return false; + } + break; + default: + return false; + } + } + return true; +} + +Form* append_deref_tokens(Form* base, const std::vector& tokens, FormPool& pool) { + if (tokens.empty()) { + return base; + } + return pool.form(base, false, tokens); +} + bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack& stack) { if (env.func->name() == "matrix-copy!" || env.func->name() == "(method 63 collide-shape-moving)") { @@ -3160,49 +3264,58 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack // first, check the loads. they should be something like source = (-> MAT vec quad) // MAT should always be a variable std::vector load_src_ras, store_dest_ras, store_src_ras; + std::vector src_matrix_tokens, dst_matrix_tokens; for (int i = 0; i < 4; i++) { - Matcher deref_matcher; if (env.version == GameVersion::Jak1) { - deref_matcher = Matcher::deref( - Matcher::any_reg(0), false, - {DerefTokenMatcher::string("vector"), DerefTokenMatcher::integer(i), - DerefTokenMatcher::string("quad")}); + auto row = match_jak1_matrix_row(matrix_entries->at(i).source, i, env); + if (!row || (i && !same_deref_tokens(src_matrix_tokens, row->matrix_tokens, env))) { + return false; + } + load_src_ras.push_back(row->base); + if (!i) { + src_matrix_tokens = std::move(row->matrix_tokens); + } } else { const char* names[] = {"rvec", "uvec", "fvec", "trans"}; - deref_matcher = - Matcher::deref(Matcher::any_reg(0), false, - {DerefTokenMatcher::string(names[i]), - DerefTokenMatcher::string("quad")}); + auto deref_matcher = Matcher::deref( + Matcher::any_reg(0), false, + {DerefTokenMatcher::string(names[i]), DerefTokenMatcher::string("quad")}); + auto mr = match(deref_matcher, matrix_entries->at(i).source, &env); + if (!mr.matched) { + return false; + } + load_src_ras.push_back(mr.maps.regs.at(0).value()); } - auto mr = match(deref_matcher, matrix_entries->at(i).source, &env); - if (!mr.matched) { - return false; - } - load_src_ras.push_back(mr.maps.regs.at(0).value()); } // check the stores for (int i = 4; i < 8; i++) { - Matcher dst_matcher; if (env.version == GameVersion::Jak1) { - dst_matcher = Matcher::deref( - Matcher::any_reg(0), false, - {DerefTokenMatcher::string("vector"), DerefTokenMatcher::integer(i - 4), - DerefTokenMatcher::string("quad")}); + auto* set = dynamic_cast(matrix_entries->at(i).elt); + auto row = set ? match_jak1_matrix_row(set->dst(), i - 4, env) : std::nullopt; + auto src = set ? identity_var(set->src()) : std::nullopt; + if (!row || !src || + (i != 4 && !same_deref_tokens(dst_matrix_tokens, row->matrix_tokens, env))) { + return false; + } + store_dest_ras.push_back(row->base); + store_src_ras.push_back(*src); + if (i == 4) { + dst_matrix_tokens = std::move(row->matrix_tokens); + } } else { const char* names[] = {"rvec", "uvec", "fvec", "trans"}; - dst_matcher = - Matcher::deref(Matcher::any_reg(0), false, - {DerefTokenMatcher::string(names[i - 4]), - DerefTokenMatcher::string("quad")}); + auto dst_matcher = Matcher::deref( + Matcher::any_reg(0), false, + {DerefTokenMatcher::string(names[i - 4]), DerefTokenMatcher::string("quad")}); + Matcher matcher = Matcher::set(dst_matcher, Matcher::any_reg(1)); + auto mr = match(matcher, matrix_entries->at(i).elt, &env); + if (!mr.matched) { + return false; + } + store_dest_ras.push_back(mr.maps.regs.at(0).value()); + store_src_ras.push_back(mr.maps.regs.at(1).value()); } - Matcher matcher = Matcher::set(dst_matcher, Matcher::any_reg(1)); - auto mr = match(matcher, matrix_entries->at(i).elt, &env); - if (!mr.matched) { - return false; - } - store_dest_ras.push_back(mr.maps.regs.at(0).value()); - store_src_ras.push_back(mr.maps.regs.at(1).value()); } // check loads are all loading from the same matrix @@ -3226,13 +3339,15 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack } } - // check types - if (env.get_variable_type(load_src_ras.at(0), true) != TypeSpec("matrix")) { - return false; - } + // For Jak 1, match_jak1_matrix_row checked the types after following the dereference prefix. + if (env.version != GameVersion::Jak1) { + if (env.get_variable_type(load_src_ras.at(0), true) != TypeSpec("matrix")) { + return false; + } - if (env.get_variable_type(store_dest_ras.at(0), true) != TypeSpec("matrix")) { - return false; + if (env.get_variable_type(store_dest_ras.at(0), true) != TypeSpec("matrix")) { + return false; + } } stack.pop(8); @@ -3248,6 +3363,7 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack } else { // lg::info(" popped src: {}", src_repopped->to_string(env)); } + src_repopped = append_deref_tokens(src_repopped, src_matrix_tokens, pool); // src_repopped = matrix_entries->at(0).source; bool found = false; @@ -3261,8 +3377,11 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack // lg::info(" popped dst: {} {} {}", dst_repopped->to_string(env), ra.reg().to_string(), // ra.mode() == AccessMode::WRITE); } + dst_repopped = append_deref_tokens(dst_repopped, dst_matrix_tokens, pool); - if (found) { + // If the matrix is a field of the popped value, the copy result is not the value of that + // containing object. Emit it as a standalone side-effecting form instead. + if (found && dst_matrix_tokens.empty()) { stack.push_value_to_reg( ra, pool.form( @@ -7094,7 +7213,19 @@ void TypeOfElement::update_from_stack(const Env& env, std::vector* result, bool allow_side_effects) { mark_popped(); - value->update_children_from_stack(env, pool, stack, allow_side_effects); + // The CFG rewrite replaces several uses in the expanded runtime-type check with this single + // expression. The original register-use analysis therefore does not mark the input as consumed, + // even though rtype-of is now its last use. Supply the post-rewrite consumption explicitly so a + // one-use input can still be propagated into rtype-of. + auto value_var = identity_var(value); + if (value_var) { + value = + pop_to_forms({*value_var}, env, pool, stack, allow_side_effects, RegSet{value_var->reg()}) + .at(0); + value->parent_element = this; + } else { + value->update_children_from_stack(env, pool, stack, allow_side_effects); + } result->push_back(this); } //////////////////////// diff --git a/decompiler/config/jak1/all-types.gc b/decompiler/config/jak1/all-types.gc index b24bedcd54..307396bbd0 100644 --- a/decompiler/config/jak1/all-types.gc +++ b/decompiler/config/jak1/all-types.gc @@ -17405,6 +17405,30 @@ its matching records and each allocation bitmap begins with every slot free." (defenum cam-slave-options :bitfield #t + :type uint32 + (BUTT_CAM) + (SAME_SIDE) + (MOVE_SPHERICAL) + (ALLOW_Z_ROT) + (JUMP_PITCHES) + (COLLIDE) + (FIND_HIDDEN_TARGET) + (DRAG) + (PLAYER_MOVING_CAMERA) + (LINE_OF_SIGHT) + (MOVEMENT_BLOCKED) + (SHRINK_MAX_ANGLE) + (GOTO_GOOD_POINT) + (BLOCK_SHIFT_BUTTONS) + (BIKE_MODE) + (NO_ROTATE) + (STICKY_ANGLE) + (AIR_EXIT) + ) + +(defenum cam-slave-options-i + :bitfield #t + :type int32 (BUTT_CAM) (SAME_SIDE) (MOVE_SPHERICAL) @@ -17440,6 +17464,25 @@ its matching records and each allocation bitmap begins with every slot free." (between 3) ) +(defenum cam-track-status + :type uint64 + (use-slave-tracking 0) + (track-at-combiner 1) + (track-at-dst 2) + (track-at-src 3)) + +(defenum cam-master-options + :type uint32 + :bitfield #t + (ignore-regions 0) + (have-target 1) + (switch-only-on-ground 2) + (set-combiner-axis 3) + (flip-combiner 4) + (have-ease-to-pos 5) + (in-base-region 6) + ) + ;; - Types ;; Shared gameplay-camera tuning used for collision movement, input response, @@ -17641,8 +17684,8 @@ its matching records and each allocation bitmap begins with every slot free." (dist-from-dest float :offset-assert 208) (flip-control-axis vector :inline :offset-assert 224) (velocity vector :inline :offset-assert 240) - (tracking-status uint64 :offset-assert 256) - (tracking-options int32 :offset-assert 264) + (tracking-status cam-track-status :offset-assert 256) + (tracking-options cam-slave-options-i :offset-assert 264) (tracking cam-rotation-tracker :inline :offset-assert 272) ) :heap-base #x170 @@ -17675,7 +17718,7 @@ its matching records and each allocation bitmap begins with every slot free." (circular-follow vector :inline :offset-assert 2176) (max-angle-offset float :offset-assert 2192) (max-angle-curr float :offset-assert 2196) - (options uint32 :offset-assert 2200) + (options cam-slave-options :offset-assert 2200) (cam-entity entity :offset-assert 2204) ; not totally confirmed yet, could be entity-actor (velocity vector :inline :offset-assert 2208) (desired-pos vector :inline :offset-assert 2224) @@ -17745,7 +17788,7 @@ its matching records and each allocation bitmap begins with every slot free." ;; active slaves, target transforms and tracking trail, and transition state; ;; the combiner produces the final rendered camera. (deftype camera-master (process) - ((master-options uint32 :offset-assert 112) + ((master-options cam-master-options :offset-assert 112) (num-slaves int32 :offset-assert 116) (slave (pointer camera-slave) 2 :offset-assert 120) (slave-options uint32 :offset-assert 128) @@ -24540,8 +24583,8 @@ optional bottom depth. Enable ordinary water particles after loading the data." lead with a quartic transition." (function cam-rotation-tracker vector symbol vector)) (define-extern slave-set-rotation! "Build tracker's inverse camera rotation from its follow point, optional point of interest and tilt; keep the target in frame, optionally blend toward the new - orientation, and remove roll. options-bits is a raw cam-slave-options word carried in a float." - (function cam-rotation-tracker vector float float symbol none)) + orientation, and remove roll." + (function cam-rotation-tracker vector cam-slave-options float symbol none)) (define-extern camera-slave-debug (function camera-slave none)) (define-extern camera-line-rel-len (function vector vector float vector4w none)) (define-extern cam-slave-get-flags "Read property's base flag word, set bits from property-on, @@ -30392,14 +30435,6 @@ from the entity name, then load the art group named by the entity type.")) (gouraud 2) ) -;; Known camera-master settings exposed on the debug menu. -(defenum cam-master-options - :type uint64 - :bitfield #t - (ignore-regions 0) - (switch-only-on-ground 2) - ) - ;; - Functions (define-extern build-continue-menu diff --git a/decompiler/config/jak1/ntsc_v1/hacks.jsonc b/decompiler/config/jak1/ntsc_v1/hacks.jsonc index 35df7d3275..0f5a4dd689 100644 --- a/decompiler/config/jak1/ntsc_v1/hacks.jsonc +++ b/decompiler/config/jak1/ntsc_v1/hacks.jsonc @@ -309,6 +309,8 @@ // that they used the correct number. This will override the decompiler's // automatic detection. "bad_format_strings": { + "ERROR : missing camera-slave parameter to *camera-combiner* start-tracking~%": 1, + "ERROR : missing camera-slave parameter to *camera-combiner* copy-tracking~%": 1, "ERROR: dma tag has data in reserved bits ~X~%": 0, "#: value of symbol ~A in task-controls is not a task-control~%": 0, diff --git a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc index d57989c3c4..181f1986d2 100644 --- a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc +++ b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc @@ -1353,7 +1353,7 @@ ], "(event cam-combiner-active)": [ [10, "a0", "vector"], - [[99, 127], "gp", "camera-slave"], + [[99, 128], "gp", "camera-slave"], [[187, 231], "gp", "camera-slave"] ], "(method 15 tracking-spline)": [ diff --git a/decompiler/config/jak1/ntsc_v1/var_names.jsonc b/decompiler/config/jak1/ntsc_v1/var_names.jsonc index 527312ee9d..0baac72074 100644 --- a/decompiler/config/jak1/ntsc_v1/var_names.jsonc +++ b/decompiler/config/jak1/ntsc_v1/var_names.jsonc @@ -6826,28 +6826,8 @@ }, "(event cam-combiner-active)": { "vars": { - "t9-2": "print-error-fn", - "a0-15": "output-stream", - "a1-3": "error-message", - "v1-7": "argument", - "t9-3": "type-check-fn", - "v1-8": "argument", - "t9-4": "print-error-fn", - "a0-18": "output-stream", - "a1-5": "error-message", - "v1-10": "argument", "gp-1": ["source-slave", "camera-slave"], "gp-2": ["source-position", "vector"], - "t9-10": "print-error-fn", - "a0-27": "output-stream", - "a1-11": "error-message", - "v1-23": "argument", - "t9-11": "type-check-fn", - "v1-24": "argument", - "t9-12": "print-error-fn", - "a0-30": "output-stream", - "a1-13": "error-message", - "v1-25": "argument", "gp-3": "source-slave", "a2-17": "destination-tracker", "a3-3": "source-tracker", diff --git a/goal_src/jak1/engine/anim/aligner-h.gc b/goal_src/jak1/engine/anim/aligner-h.gc index 142204f24e..24882a009b 100644 --- a/goal_src/jak1/engine/anim/aligner-h.gc +++ b/goal_src/jak1/engine/anim/aligner-h.gc @@ -29,23 +29,23 @@ ;; DECOMP BEGINS -;; Animation alignment turns motion authored on the skeleton's alignment joint -;; into movement of the owning process. Each update samples the align joint and -;; measures translation, scale, and rotation relative to the previous sample. -;; The delta is disabled when the animation changes or crosses a -;; loop boundary, where subtracting the two samples would create a false jump. +;; Align-control moves a process through an animation. +;; The "align" joint of the animation defines how the root of the process +;; drawable should move. +;; Each update samples the align joint and measures translation, scale, and +;; rotation relative to the previous sample. ;; matrix and transform use index 0 for the current sample and index 1 for the ;; previous sample. (deftype align-control (basic) ((flags align-flags) (process process-drawable) - (frame-group art-joint-anim) ;; animation sampled during the previous update - (frame-num float) ;; frame sampled during the previous update - (matrix matrix 2 :inline) ;; current and previous alignment-joint matrices + (frame-group art-joint-anim) ;; animation sampled during the previous update + (frame-num float) ;; frame sampled during the previous update + (matrix matrix 2 :inline) ;; current and previous alignment-joint matrices (transform transformq 2 :inline) ;; decomposed current and previous samples - (delta transformq :inline) ;; current sample relative to the previous sample - (last-speed meters) ;; last horizontal alignment speed + (delta transformq :inline) ;; current sample relative to the previous sample + (last-speed meters) ;; last horizontal alignment speed ;; Working transformq view of the current transform. (align transformq :inline :overlay-at (-> transform 0 trans x))) (:methods diff --git a/goal_src/jak1/engine/anim/aligner.gc b/goal_src/jak1/engine/anim/aligner.gc index 4e7e385719..81b3b1b287 100644 --- a/goal_src/jak1/engine/anim/aligner.gc +++ b/goal_src/jak1/engine/anim/aligner.gc @@ -6,11 +6,11 @@ ;; DECOMP BEGINS -;; Animation alignment turns motion authored on the skeleton's alignment joint -;; into movement of the owning process. Each update samples the align joint and -;; measures translation, scale, and rotation relative to the previous sample. -;; The delta is disabled when the animation changes or crosses a -;; loop boundary, where subtracting the two samples would create a false jump. +;; Align-control moves a process through an animation. +;; The "align" joint of the animation defines how the root of the process +;; drawable should move. +;; Each update samples the align joint and measures translation, scale, and +;; rotation relative to the previous sample. ;; matrix and transform use index 0 for the current sample and index 1 for the ;; previous sample. @@ -20,38 +20,40 @@ the first frame of a non-looping animation so the discontinuity is not mistaken for root motion." (local-vars (disable-alignment? symbol)) (with-pp - ;; Stack commands do not play their own frame group. Every other active channel must reference - ;; joint animation data before the alignment joint can be evaluated safely. + ;; Verify the joint animations are correct before any evaluation. (let ((active-channel-count (-> this process skel active-channels))) (dotimes (channel-index active-channel-count) (let* ((channel (-> this process skel channel channel-index)) - (channel-frame-group (-> channel frame-group)) - (channel-command (-> channel command)) - (stack-command 'stack) - (stack-command? (= channel-command stack-command))) - (cond - ((or stack-command? (begin (set! stack-command 'stack1) (= channel-command stack-command)))) + (channel-frame-group (-> channel frame-group))) + ;; og:preserve-this + (case (-> channel command) + (('stack 'stack1) + ;; stack commands don't introduce a new frame-group. + ) (else - (when (!= (-> channel-frame-group type) art-joint-anim) - ;; og:preserve-this - (go process-drawable-art-error "align joint-anim") - (abandon-thread) - 0)))))) + (when (!= (-> channel-frame-group type) art-joint-anim) + (go process-drawable-art-error "align joint-anim") + (abandon-thread))))))) ;; Disable alignment on frame-group switching, looping, or starting a new animation. (let* ((root-channel (-> this process skel root-channel 0)) (current-frame-group (-> root-channel frame-group)) (current-frame (-> root-channel frame-num))) (= (-> root-channel num-func) num-func-loop!) (cond + ;; switch frame-group ((or (not current-frame-group) (!= (-> this frame-group) current-frame-group)) (set! disable-alignment? #t)) + ;; loop wrap ((= (-> root-channel num-func) num-func-loop!) (set! disable-alignment? (< (* (-> root-channel param 0) (- current-frame (-> this frame-num))) 0.0))) + ;; anim start (else (set! disable-alignment? (= current-frame 0.0)))) - (if disable-alignment? (logior! (-> this flags) (align-flags disabled)) (logclear! (-> this flags) (align-flags disabled))) + (if disable-alignment? + (logior! (-> this flags) (align-flags disabled)) + (logclear! (-> this flags) (align-flags disabled))) (set! (-> this frame-group) current-frame-group) (set! (-> this frame-num) current-frame)) ;; Shift the decomposed transform and source matrix into the previous-sample slots. - (mem-copy! (the-as pointer (-> this transform 1)) (the-as pointer (-> this transform)) 48) + (mem-copy! (the-as pointer (-> this transform 1)) (the-as pointer (-> this transform 0)) 48) (quaternion-copy! (-> this transform 1 quat) (-> this align quat)) (vector-copy! (-> this transform 1 scale) (-> this align scale)) (matrix-copy! (-> this matrix 1) (-> this matrix 0)) @@ -64,7 +66,7 @@ ;; This "no-push" evaluation mode is a clever solution. A single group of animations are still interpolated, ;; but cross-fades between animations (fade in/fade out) are not. ;; For example, if there's a mix of run-left and run-straight, we'll interpolate the alignment. - ;; But, if were to fade into a "jump" by doing a (push jump), (stack) on top of the run, + ;; But, if were to fade into a "jump" by doing a (push jump), (stack) on top of the run, ;; the "no-push" mode would make the jump animation override the run animation. ;; this is only applied for the alignment matrix. (cspace<-matrix-no-push-joint! alignment-joint (-> this process skel)) diff --git a/goal_src/jak1/engine/camera/cam-combiner.gc b/goal_src/jak1/engine/camera/cam-combiner.gc index 6fe9101579..b57cfe6012 100644 --- a/goal_src/jak1/engine/camera/cam-combiner.gc +++ b/goal_src/jak1/engine/camera/cam-combiner.gc @@ -6,30 +6,35 @@ ;; DECOMP BEGINS -;; The combiner is the single camera transform consumed by rendering. During a camera change it -;; blends the outgoing slave (slot 0) to the incoming slave (slot 1), then retires the outgoing -;; process. Positions and field of view use the eased interpolation parameter; orientation uses the -;; axis-angle construction described below so the basis stays orthonormal throughout the change. -;; -;; tracking-status selects where target-follow rotation is evaluated: -;; 0 - use each slave's own rotation -;; 1 - evaluate tracking once at the blended camera position -;; 2 - evaluate tracking for the destination endpoint -;; 3 - evaluate tracking for the source endpoint -;; The endpoint-only modes bridge camera states with different tracking policies. Once the outgoing -;; slave is removed, destination tracking becomes mode 1 and source tracking becomes mode 0. -;; start-tracking adopts a slave's tracking controls and recalculates its follow direction at the -;; slave position. copy-tracking additionally preserves the slave's current follow points and basis, -;; which avoids a visible jump when tracking begins from an already active camera. +;; Blend the active camera slaves into the single transform used for rendering. +;; The complexity here is around camera orientation. Orientations are interpolated +;; in a way that reduces motion sickness. The combiner can optionally recompute tracking, +;; which helps aim the combined camera at the target. + +;; Messages: +;; (point-of-interest (pos [vector|#f])) +;; Blend tracking toward pos, or disable point-of-interest tracking when pos is #f. +;; (set-interpolation (duration time-frame)) +;; Restart the current slave blend and derive its update step from duration. +;; (teleport) +;; Recalculate combiner-owned tracking immediately after a discontinuous camera move. +;; (stop-tracking) +;; Stop calculating tracking in the combiner and use the slaves' rotations instead. +;; (start-tracking (source camera-slave)) +;; Adopt source's tracking controls and calculate fresh follow state at its position. +;; (copy-tracking (source camera-slave)) +;; Adopt source's tracking controls together with its current follow state and rotation, +;; avoiding a discontinuity when an already tracking slave is handed to the combiner. (defstate cam-combiner-active (camera-combiner) :event (behavior ((proc process) (argc int) (message symbol) (block event-message-block)) + (local-vars (source-slave camera-slave)) (case message (('point-of-interest) (cond ((-> block param 0) (set! (-> self tracking use-point-of-interest) #t) - (vector-copy! (-> self tracking point-of-interest) (the-as vector (-> block param 0))) + (set! (-> self tracking point-of-interest quad) (-> (the-as vector (-> block param 0)) quad)) (set! (-> self tracking point-of-interest-blend target) 1.0)) (else (set! (-> self tracking use-point-of-interest) #f) (set! (-> self tracking point-of-interest-blend target) 0.0)))) (('set-interpolation) @@ -38,215 +43,249 @@ (('teleport) (when (nonzero? (-> self tracking-status)) (cam-calc-follow! (-> self tracking) (-> self trans) #f) - (slave-set-rotation! (-> self tracking) (-> self trans) (the-as float (-> self tracking-options)) (-> self fov) #f))) - (('stop-tracking) (set! (-> self tracking-status) (the-as uint 0)) 0) + (slave-set-rotation! (-> self tracking) + (-> self trans) + (the-as cam-slave-options (-> self tracking-options)) + (-> self fov) + #f))) + (('stop-tracking) (set! (-> self tracking-status) (cam-track-status use-slave-tracking)) 0) (('start-tracking) (cond ((< argc 1) - (let ((print-error-fn format) - (output-stream 0) - (error-message "ERROR : missing camera-slave parameter to *camera-combiner* start-tracking~%")) - (let ((argument (-> block param 0))) (rtype-of argument)) - (print-error-fn output-stream error-message))) - ((let ((type-check-fn type-type?) - (argument (-> block param 0))) - (not (type-check-fn (rtype-of argument) camera-slave))) - (let ((print-error-fn format) - (output-stream 0) - (error-message "ERROR : invalid type '~A' to *camera-combiner* start-tracking~%") - (argument (-> block param 0))) - (print-error-fn output-stream error-message (rtype-of argument)))) - ((zero? (-> self tracking-status)) - (set! (-> self tracking-status) (the-as uint 1)) - (let ((source-slave (the-as camera-slave (-> block param 0)))) - (set! (-> self tracking-options) (the-as int (-> source-slave options))) - (set! (-> self tracking no-follow) (-> source-slave tracking no-follow)) - (copy-cam-float-seeker (-> self tracking tilt-adjust) (-> source-slave tracking tilt-adjust)) - (copy-cam-float-seeker (-> self tracking underwater-blend) (-> source-slave tracking underwater-blend)) - (set! (-> self tracking use-point-of-interest) (-> source-slave tracking use-point-of-interest)) - (vector-copy! (-> self tracking point-of-interest) (-> source-slave tracking point-of-interest)) - (copy-cam-float-seeker (-> self tracking point-of-interest-blend) (-> source-slave tracking point-of-interest-blend)) - (let ((source-position (-> source-slave trans))) - (cam-calc-follow! (-> self tracking) source-position #f) - (slave-set-rotation! (-> self tracking) source-position (the-as float (-> self tracking-options)) (-> self fov) #f)))))) + (format 0 + "ERROR : missing camera-slave parameter to *camera-combiner* start-tracking~%" + (rtype-of (-> block param 0)))) + ((not (type-type? (rtype-of (-> block param 0)) camera-slave)) + (format 0 "ERROR : invalid type '~A' to *camera-combiner* start-tracking~%" (rtype-of (-> block param 0)))) + ((= (-> self tracking-status) (cam-track-status use-slave-tracking)) + (set! (-> self tracking-status) (cam-track-status track-at-combiner)) + (set! source-slave (the-as camera-slave (-> block param 0))) + (set! (-> self tracking-options) (the-as cam-slave-options-i (-> source-slave options))) + (set! (-> self tracking no-follow) (-> source-slave tracking no-follow)) + (copy-cam-float-seeker (-> self tracking tilt-adjust) (-> source-slave tracking tilt-adjust)) + (copy-cam-float-seeker (-> self tracking underwater-blend) (-> source-slave tracking underwater-blend)) + (set! (-> self tracking use-point-of-interest) (-> source-slave tracking use-point-of-interest)) + (vector-copy! (-> self tracking point-of-interest) (-> source-slave tracking point-of-interest)) + (copy-cam-float-seeker (-> self tracking point-of-interest-blend) (-> source-slave tracking point-of-interest-blend)) + (let ((source-position (-> source-slave trans))) + (cam-calc-follow! (-> self tracking) source-position #f) + (slave-set-rotation! (-> self tracking) + source-position + (the-as cam-slave-options (-> self tracking-options)) + (-> self fov) + #f))))) (('copy-tracking) (cond ((< argc 1) - (let ((print-error-fn format) - (output-stream 0) - (error-message "ERROR : missing camera-slave parameter to *camera-combiner* copy-tracking~%")) - (let ((argument (-> block param 0))) (rtype-of argument)) - (print-error-fn output-stream error-message))) - ((let ((type-check-fn type-type?) - (argument (-> block param 0))) - (not (type-check-fn (rtype-of argument) camera-slave))) - (let ((print-error-fn format) - (output-stream 0) - (error-message "ERROR : invalid type '~A' to *camera-combiner* copy-tracking~%") - (argument (-> block param 0))) - (print-error-fn output-stream error-message (rtype-of argument)))) + (format 0 + "ERROR : missing camera-slave parameter to *camera-combiner* copy-tracking~%" + (rtype-of (-> block param 0)))) + ((not (type-type? (rtype-of (-> block param 0)) camera-slave)) + (format 0 "ERROR : invalid type '~A' to *camera-combiner* copy-tracking~%" (rtype-of (-> block param 0)))) ((nonzero? (-> self tracking-status)) #f) (else - (set! (-> self tracking-status) (the-as uint 1)) - (let ((source-slave (the-as camera-slave (-> block param 0)))) - (set! (-> self tracking-options) (the-as int (-> source-slave options))) - (set! (-> self tracking no-follow) (-> source-slave tracking no-follow)) - (copy-cam-float-seeker (-> self tracking tilt-adjust) (-> source-slave tracking tilt-adjust)) - (copy-cam-float-seeker (-> self tracking underwater-blend) (-> source-slave tracking underwater-blend)) - (vector-copy! (-> self tracking follow-off) (-> source-slave tracking follow-off)) - (vector-copy! (-> self tracking follow-pt) (-> source-slave tracking follow-pt)) - (matrix-copy! (-> self tracking inv-mat) (-> source-slave tracking inv-mat)) - (set! (-> self tracking use-point-of-interest) (-> source-slave tracking use-point-of-interest)) - (vector-copy! (-> self tracking point-of-interest) (-> source-slave tracking point-of-interest)) - (copy-cam-float-seeker (-> self tracking point-of-interest-blend) (-> source-slave tracking point-of-interest-blend)))))))) + (set! (-> self tracking-status) (cam-track-status track-at-combiner)) + (set! source-slave (the-as camera-slave (-> block param 0))) + (set! (-> self tracking-options) (the-as cam-slave-options-i (-> source-slave options))) + (set! (-> self tracking no-follow) (-> source-slave tracking no-follow)) + (copy-cam-float-seeker (-> self tracking tilt-adjust) (-> source-slave tracking tilt-adjust)) + (copy-cam-float-seeker (-> self tracking underwater-blend) (-> source-slave tracking underwater-blend)) + (vector-copy! (-> self tracking follow-off) (-> source-slave tracking follow-off)) + (vector-copy! (-> self tracking follow-pt) (-> source-slave tracking follow-pt)) + (matrix-copy! (-> self tracking inv-mat) (-> source-slave tracking inv-mat)) + (set! (-> self tracking use-point-of-interest) (-> source-slave tracking use-point-of-interest)) + (vector-copy! (-> self tracking point-of-interest) (-> source-slave tracking point-of-interest)) + (copy-cam-float-seeker (-> self tracking point-of-interest-blend) (-> source-slave tracking point-of-interest-blend))))))) :code (behavior () - (local-vars (source-tracker cam-rotation-tracker)) + (local-vars (output-matrix matrix)) + ;; tracking-status controls where target-follow rotation is evaluated: + ;; use-slave-tracking - use each slave's own rotation. + ;; track-at-combiner - evaluate once at the blended output position. + ;; track-at-dst - evaluate only the destination endpoint during a blend; this + ;; becomes track-at-combiner when the destination becomes active. + ;; track-at-src - evaluate only the source endpoint during a blend; this becomes + ;; use-slave-tracking when the source is retired. (loop - (when (and (not (logtest? (-> *camera* master-options) 2)) (!= (-> self tracking-status) 0)) - (set! (-> self tracking-status) (the-as uint 0)) + ;; Combiner tracking is meaningless once the master no longer has a target. + (when (and (not (logtest? (-> *camera* master-options) (cam-master-options have-target))) + (!= (-> self tracking-status) (cam-track-status use-slave-tracking))) + (set! (-> self tracking-status) (cam-track-status use-slave-tracking)) 0) (when *camera* (let ((source-slave (-> *camera* slave 0)) (destination-slave (-> *camera* slave 1)) (blend (parameter-ease-sin-clamp (-> self interp-val))) (previous-position (new-stack-vector0))) - (set! (-> previous-position quad) (-> self trans quad)) - (when source-slave - (cond - (destination-slave - (vector-lerp-clamp! (-> self trans) (-> source-slave 0 trans) (-> destination-slave 0 trans) blend) - (set! (-> self fov) (lerp-clamp (-> source-slave 0 fov) (-> destination-slave 0 fov) blend)) - (set! (-> self dist-from-src) (vector-vector-distance (-> self trans) (-> source-slave 0 trans))) - (set! (-> self dist-from-dest) (vector-vector-distance (-> self trans) (-> destination-slave 0 trans))) - (cond - ((= (-> self tracking-status) 1) - (cam-calc-follow! (-> self tracking) (-> self trans) #t) - (slave-set-rotation! (-> self tracking) (-> self trans) (the-as float (-> self tracking-options)) (-> self fov) #t) - (matrix-copy! (-> self inv-camera-rot) (-> self tracking inv-mat))) - (else - (set! source-tracker (-> source-slave 0 tracking)) - (let ((source-position (-> source-slave 0 trans)) - (destination-tracker (-> destination-slave 0 tracking)) - (destination-position (-> destination-slave 0 trans))) - (cond - ((= (-> self tracking-status) 3) - (cam-calc-follow! (-> self tracking) source-position #t) - (slave-set-rotation! (-> self tracking) source-position (the-as float (-> self tracking-options)) (-> self fov) #t) - (set! source-tracker (-> self tracking)) - (set! source-position (-> self trans))) - ((= (-> self tracking-status) 2) - (cam-calc-follow! (-> self tracking) destination-position #t) - (slave-set-rotation! (-> self tracking) destination-position (the-as float (-> self tracking-options)) (-> self fov) #t) - (set! destination-tracker (-> self tracking)) - (set! destination-position (-> self trans)))) - ;; Any two orthonormal camera bases differ by one axis-angle rotation. The - ;; differences between corresponding basis rows lie perpendicular to that - ;; rotation axis. Cross the two strongest differences—the smallest is omitted - ;; because its source axis is closest to the rotation axis—to obtain a stable - ;; axis even when one row barely moves. - (let ((rotation-work (new 'stack-no-clear 'matrix))) - (dotimes (i 3) - (set! (-> rotation-work vector i quad) (the-as uint128 0))) - 0.0 - 0.0 - 0.0 - (let ((rotation-axis (new-stack-vector0))) - 0.0 - (let ((rotation-matrix (new-stack-matrix0))) - (vector-! (-> rotation-work vector 0) - (the-as vector (-> source-tracker inv-mat)) - (the-as vector (-> destination-tracker inv-mat))) - (vector-! (-> rotation-work vector 1) (-> source-tracker inv-mat vector 1) (-> destination-tracker inv-mat vector 1)) - (vector-! (-> rotation-work vector 2) (-> source-tracker inv-mat vector 2) (-> destination-tracker inv-mat vector 2)) - (let ((row-0-delta (vector-length (-> rotation-work vector 0))) - (row-1-delta (vector-length (-> rotation-work vector 1))) - (row-2-delta (vector-length (-> rotation-work vector 2)))) - (cond - ((and (< row-0-delta row-1-delta) (< row-0-delta row-2-delta)) - (vector-cross! rotation-axis (-> rotation-work vector 1) (-> rotation-work vector 2))) - ((and (< row-1-delta row-0-delta) (< row-1-delta row-2-delta)) - (vector-cross! rotation-axis (-> rotation-work vector 0) (-> rotation-work vector 2))) - (else (vector-cross! rotation-axis (-> rotation-work vector 0) (-> rotation-work vector 1))))) - (vector-normalize! rotation-axis 1.0) - ;; Project the source and destination row least parallel to the chosen - ;; axis into its perpendicular plane. Their angle is the relative camera - ;; rotation; their cross product fixes the sign of the axis. - (let ((row-0-axis-dot (fabs (vector-dot (the-as vector (-> source-tracker inv-mat)) rotation-axis))) - (row-1-axis-dot (fabs (vector-dot (-> source-tracker inv-mat vector 1) rotation-axis))) - (row-2-axis-dot (fabs (vector-dot (-> source-tracker inv-mat vector 2) rotation-axis)))) - (cond - ((and (< row-0-axis-dot row-1-axis-dot) (< row-0-axis-dot row-2-axis-dot)) - (vector-flatten! (-> rotation-work vector 0) (the-as vector (-> source-tracker inv-mat)) rotation-axis) - (vector-flatten! (-> rotation-work vector 1) (the-as vector (-> destination-tracker inv-mat)) rotation-axis)) - ((< row-1-axis-dot row-2-axis-dot) - (vector-flatten! (-> rotation-work vector 0) (-> source-tracker inv-mat vector 1) rotation-axis) - (vector-flatten! (-> rotation-work vector 1) (-> destination-tracker inv-mat vector 1) rotation-axis)) - (else - (vector-flatten! (-> rotation-work vector 0) (-> source-tracker inv-mat vector 2) rotation-axis) - (vector-flatten! (-> rotation-work vector 1) (-> destination-tracker inv-mat vector 2) rotation-axis)))) - (vector-normalize! (-> rotation-work vector 0) 1.0) - (vector-normalize! (-> rotation-work vector 1) 1.0) - (vector-cross! (-> rotation-work vector 2) (-> rotation-work vector 0) (-> rotation-work vector 1)) - (if (< (vector-dot (-> rotation-work vector 2) rotation-axis) 0.0) (vector-negate! rotation-axis rotation-axis)) - (let ((rotation-angle (acos (vector-dot (-> rotation-work vector 0) (-> rotation-work vector 1))))) - ;; Bit 3 marks the first frame after adding a slave and bit 4 records - ;; use of the complementary rotation arc. Reset the old choice at the - ;; start of a transition. The target/destination geometry test ends in - ;; an empty conditional in this build and therefore does not choose an - ;; arc. On later frames, preserve the chosen axis across rotations over - ;; 90 degrees instead of letting its sign jump. - (cond - ((logtest? (-> *camera* master-options) 8) - (logand! (-> *camera* master-options) -25) - (when (and (< 8192.0 rotation-angle) (logtest? (-> *camera* master-options) 2)) - (vector-! (-> rotation-work vector 0) (-> *camera* tpos-curr) source-position) - (vector-! (-> rotation-work vector 1) destination-position source-position) - (vector-flatten! (-> rotation-work vector 0) (-> rotation-work vector 0) (-> *camera* local-down)) - (vector-flatten! (-> rotation-work vector 1) (-> rotation-work vector 1) (-> *camera* local-down)) - (when (and (< 4096.0 (vector-normalize-ret-len! (-> rotation-work vector 0) 1.0)) - (< 4096.0 (vector-normalize-ret-len! (-> rotation-work vector 1) 1.0))) - (vector-cross! (-> rotation-work vector 2) (-> rotation-work vector 1) (-> rotation-work vector 0)) - (when (< (vector-dot (-> rotation-work vector 2) rotation-axis) -0.01))))) - ((and (< 16384.0 rotation-angle) (< (vector-dot (-> self flip-control-axis) rotation-axis) 0.0)) - (logxor! (-> *camera* master-options) 16))) - (set! (-> self flip-control-axis quad) (-> rotation-axis quad)) - (when (logtest? (-> *camera* master-options) 16) - (set! rotation-angle (- 65536.0 rotation-angle)) - (vector-negate! rotation-axis rotation-axis)) - ;; Apply the portion still separating the destination from the source: - ;; a full correction at blend 0 and the destination basis at blend 1. - (let ((remaining-angle (* rotation-angle (- 1.0 blend)))) - (matrix-axis-sin-cos! rotation-matrix rotation-axis (sin remaining-angle) (cos remaining-angle)))) - (matrix*! (-> self inv-camera-rot) (-> destination-tracker inv-mat) rotation-matrix))))))) - ;; Hold interpolation while an outgoing camera curve is still reaching its exit - ;; point, and while the game is paused. This keeps the slave lifecycle aligned - ;; with the camera master's intro/outro curves. - (cond - ((and (< 0.0 (-> *camera* outro-t-step)) (< (-> *camera* outro-t) (-> *camera* outro-exit-value)))) - ((and (< (-> *camera* outro-t-step) 0.0) (< (-> *camera* outro-exit-value) (-> *camera* outro-t)))) - ((paused?)) - (else (+! (-> self interp-val) (* (-> self interp-step) (-> *display* time-adjust-ratio))))) - (when (>= (-> self interp-val) 1.0) - (deactivate (-> *camera* slave 0 0)) - (set! (-> *camera* slave 0) (-> *camera* slave 1)) - (set! (-> *camera* slave 1) (the-as (pointer camera-slave) #f)) - (+! (-> *camera* num-slaves) -1))) - (else - (set! (-> self dist-from-src) 409600.0) - (set! (-> self dist-from-dest) 0.0) - (set! (-> self trans quad) (-> source-slave 0 trans quad)) - (set! (-> self fov) (-> source-slave 0 fov)) - (cond - ((= (-> self tracking-status) 2) (set! (-> self tracking-status) (the-as uint 1))) - ((= (-> self tracking-status) 3) (set! (-> self tracking-status) (the-as uint 0)) 0)) - (cond - ((= (-> self tracking-status) 1) - (cam-calc-follow! (-> self tracking) (-> self trans) #t) - (slave-set-rotation! (-> self tracking) (-> self trans) (the-as float (-> self tracking-options)) (-> self fov) #t) - (matrix-copy! (-> self inv-camera-rot) (-> self tracking inv-mat))) - (else - (matrix-copy! (-> self inv-camera-rot) (-> source-slave 0 tracking inv-mat))))))) + (vector-copy! previous-position (-> self trans)) + (if source-slave + (set! output-matrix + (cond + (destination-slave + ;; Position and FOV can be interpolated directly. Rotation is blended as + ;; an axis-angle below so that the resulting camera basis stays orthonormal. + (vector-lerp-clamp! (-> self trans) (-> source-slave 0 trans) (-> destination-slave 0 trans) blend) + (set! (-> self fov) (lerp-clamp (-> source-slave 0 fov) (-> destination-slave 0 fov) blend)) + (set! (-> self dist-from-src) (vector-vector-distance (-> self trans) (-> source-slave 0 trans))) + (set! (-> self dist-from-dest) (vector-vector-distance (-> self trans) (-> destination-slave 0 trans))) + (set! output-matrix + (cond + ((= (-> self tracking-status) (cam-track-status track-at-combiner)) + (cam-calc-follow! (-> self tracking) (-> self trans) #t) + (slave-set-rotation! (-> self tracking) + (-> self trans) + (the-as cam-slave-options (-> self tracking-options)) + (-> self fov) + #t) + (matrix-copy! (-> self inv-camera-rot) (-> self tracking inv-mat))) + (else + (let ((source-tracker (-> source-slave 0 tracking)) + (source-position (-> source-slave 0 trans)) + (destination-tracker (-> destination-slave 0 tracking)) + (destination-position (-> destination-slave 0 trans))) + (cond + ((= (-> self tracking-status) (cam-track-status track-at-src)) + (cam-calc-follow! (-> self tracking) source-position #t) + (slave-set-rotation! (-> self tracking) + source-position + (the-as cam-slave-options (-> self tracking-options)) + (-> self fov) + #t) + (set! source-tracker (-> self tracking)) + (set! source-position (-> self trans))) + ((= (-> self tracking-status) (cam-track-status track-at-dst)) + (cam-calc-follow! (-> self tracking) destination-position #t) + (slave-set-rotation! (-> self tracking) + destination-position + (the-as cam-slave-options (-> self tracking-options)) + (-> self fov) + #t) + (set! destination-tracker (-> self tracking)) + (set! destination-position (-> self trans)))) + ;; Corresponding rows of two rotation bases differ in directions + ;; perpendicular to their shared axis of rotation. Cross the two + ;; strongest row deltas, omitting the row which changed least, to + ;; recover a stable rotation axis. + (let ((rotation-work (new 'stack-no-clear 'matrix))) + (dotimes (i 3) + (set! (-> rotation-work vector i quad) (the-as uint128 0))) + 0.0 + 0.0 + 0.0 + (let ((rotation-axis (new-stack-vector0))) + 0.0 + (let ((rotation-matrix (new-stack-matrix0))) + (vector-! (-> rotation-work vector 0) + (the-as vector (-> source-tracker inv-mat)) + (the-as vector (-> destination-tracker inv-mat))) + (vector-! (-> rotation-work vector 1) (-> source-tracker inv-mat vector 1) (-> destination-tracker inv-mat vector 1)) + (vector-! (-> rotation-work vector 2) (-> source-tracker inv-mat vector 2) (-> destination-tracker inv-mat vector 2)) + (let ((row-0-delta (vector-length (-> rotation-work vector 0))) + (row-1-delta (vector-length (-> rotation-work vector 1))) + (row-2-delta (vector-length (-> rotation-work vector 2)))) + (cond + ((and (< row-0-delta row-1-delta) (< row-0-delta row-2-delta)) + (vector-cross! rotation-axis (-> rotation-work vector 1) (-> rotation-work vector 2))) + ((and (< row-1-delta row-0-delta) (< row-1-delta row-2-delta)) + (vector-cross! rotation-axis (-> rotation-work vector 0) (-> rotation-work vector 2))) + (else (vector-cross! rotation-axis (-> rotation-work vector 0) (-> rotation-work vector 1))))) + (vector-normalize! rotation-axis 1.0) + ;; Choose the source basis row least parallel to the axis, + ;; then project that row and its destination counterpart into + ;; the plane normal to the axis. Their planar angle is the + ;; amount of rotation separating the two camera bases. + (let ((row-0-axis-dot (fabs (vector-dot (the-as vector (-> source-tracker inv-mat)) rotation-axis))) + (row-1-axis-dot (fabs (vector-dot (-> source-tracker inv-mat vector 1) rotation-axis))) + (row-2-axis-dot (fabs (vector-dot (-> source-tracker inv-mat vector 2) rotation-axis)))) + (cond + ((and (< row-0-axis-dot row-1-axis-dot) (< row-0-axis-dot row-2-axis-dot)) + (vector-flatten! (-> rotation-work vector 0) (the-as vector (-> source-tracker inv-mat)) rotation-axis) + (vector-flatten! (-> rotation-work vector 1) (the-as vector (-> destination-tracker inv-mat)) rotation-axis)) + ((< row-1-axis-dot row-2-axis-dot) + (vector-flatten! (-> rotation-work vector 0) (-> source-tracker inv-mat vector 1) rotation-axis) + (vector-flatten! (-> rotation-work vector 1) (-> destination-tracker inv-mat vector 1) rotation-axis)) + (else + (vector-flatten! (-> rotation-work vector 0) (-> source-tracker inv-mat vector 2) rotation-axis) + (vector-flatten! (-> rotation-work vector 1) (-> destination-tracker inv-mat vector 2) rotation-axis)))) + (vector-normalize! (-> rotation-work vector 0) 1.0) + (vector-normalize! (-> rotation-work vector 1) 1.0) + (vector-cross! (-> rotation-work vector 2) (-> rotation-work vector 0) (-> rotation-work vector 1)) + ;; Orient the otherwise sign-ambiguous axis to agree with the + ;; direction from the projected source row to destination row. + (if (< (vector-dot (-> rotation-work vector 2) rotation-axis) 0.0) (vector-negate! rotation-axis rotation-axis)) + (let ((rotation-angle (acos (vector-dot (-> rotation-work vector 0) (-> rotation-work vector 1))))) + (cond + ;; A newly activated slave starts a new arc decision, so + ;; discard both the one-shot marker and the previous flip. + ((logtest? (-> *camera* master-options) (cam-master-options set-combiner-axis)) + (logclear! (-> *camera* master-options) (cam-master-options set-combiner-axis flip-combiner)) + ;; Compare the target and destination directions in the + ;; local horizontal plane. The final test has no body in + ;; this version and therefore has no observable effect. + (when (and (< 8192.0 rotation-angle) (logtest? (-> *camera* master-options) (cam-master-options have-target))) + (vector-! (-> rotation-work vector 0) (-> *camera* tpos-curr) source-position) + (vector-! (-> rotation-work vector 1) destination-position source-position) + (vector-flatten! (-> rotation-work vector 0) (-> rotation-work vector 0) (-> *camera* local-down)) + (vector-flatten! (-> rotation-work vector 1) (-> rotation-work vector 1) (-> *camera* local-down)) + (when (and (< 4096.0 (vector-normalize-ret-len! (-> rotation-work vector 0) 1.0)) + (< 4096.0 (vector-normalize-ret-len! (-> rotation-work vector 1) 1.0))) + (vector-cross! (-> rotation-work vector 2) (-> rotation-work vector 1) (-> rotation-work vector 0)) + (when (< (vector-dot (-> rotation-work vector 2) rotation-axis) -0.01))))) + ;; For rotations over 90 degrees, keep the chosen axis + ;; continuous across frames by switching to the equivalent + ;; complementary axis-angle representation when necessary. + ((and (< 16384.0 rotation-angle) (< (vector-dot (-> self flip-control-axis) rotation-axis) 0.0)) + (logxor! (-> *camera* master-options) (cam-master-options flip-combiner)))) + (vector-copy! (-> self flip-control-axis) rotation-axis) + (when (logtest? (-> *camera* master-options) (cam-master-options flip-combiner)) + (set! rotation-angle (- 65536.0 rotation-angle)) + (vector-negate! rotation-axis rotation-axis)) + ;; Rotate backward from the destination basis by the portion + ;; still separating it from the source: the full angle at + ;; blend 0 and no correction at blend 1. + (let ((remaining-angle (* rotation-angle (- 1.0 blend)))) + (matrix-axis-sin-cos! rotation-matrix rotation-axis (sin remaining-angle) (cos remaining-angle)))) + (matrix*! (-> self inv-camera-rot) (-> destination-tracker inv-mat) rotation-matrix))))) + output-matrix))) + ;; Do not advance the blend while the outgoing slave is still reaching its + ;; authored exit point or while the game is paused. + (cond + ((and (< 0.0 (-> *camera* outro-t-step)) (< (-> *camera* outro-t) (-> *camera* outro-exit-value)))) + ((and (< (-> *camera* outro-t-step) 0.0) (< (-> *camera* outro-exit-value) (-> *camera* outro-t)))) + ((paused?)) + (else (+! (-> self interp-val) (* (-> self interp-step) (-> *display* time-adjust-ratio))))) + ;; Once the blend reaches its destination, retire the outgoing slave and + ;; promote the destination into slot 0. + (when (>= (-> self interp-val) 1.0) + (deactivate (-> *camera* slave 0 0)) + (set! (-> *camera* slave 0) (-> *camera* slave 1)) + (set! (-> *camera* slave 1) (the-as (pointer camera-slave) #f)) + (+! (-> *camera* num-slaves) -1)) + output-matrix) + (else + (set! (-> self dist-from-src) 409600.0) + (set! (-> self dist-from-dest) 0.0) + (vector-copy! (-> self trans) (-> source-slave 0 trans)) + (set! (-> self fov) (-> source-slave 0 fov)) + ;; Resolve the endpoint-only tracking modes after a transition has left just + ;; one slave: destination tracking becomes steady combiner tracking, while + ;; source tracking ends with the retired source. + (cond + ((= (-> self tracking-status) (cam-track-status track-at-dst)) + (set! (-> self tracking-status) (cam-track-status track-at-combiner))) + ((= (-> self tracking-status) (cam-track-status track-at-src)) + (set! (-> self tracking-status) (cam-track-status use-slave-tracking)) + 0)) + (cond + ((= (-> self tracking-status) (cam-track-status track-at-combiner)) + (cam-calc-follow! (-> self tracking) (-> self trans) #t) + (slave-set-rotation! (-> self tracking) + (-> self trans) + (the-as cam-slave-options (-> self tracking-options)) + (-> self fov) + #t) + (matrix-copy! (-> self inv-camera-rot) (-> self tracking inv-mat))) + (else (matrix-copy! (-> self inv-camera-rot) (-> source-slave 0 tracking inv-mat)))))))) (vector-! (-> self velocity) (-> self trans) previous-position))) (if (and *dproc* *debug-segment*) (add-frame (-> *display* frames (-> *display* on-screen) frame profile-bar 0) 'camera (new 'static 'rgba :b #xff :a #x80))) @@ -262,7 +301,7 @@ (if *math-camera* (set! (-> self fov) (-> *math-camera* fov)) (set! (-> self fov) 11650.845)) (set! (-> self interp-val) 0.0) (set! (-> self interp-step) 0.125) - (set! (-> self tracking-status) (the-as uint 0)) + (set! (-> self tracking-status) (cam-track-status use-slave-tracking)) (vector-reset! (-> self velocity)) (go cam-combiner-active) 0 diff --git a/goal_src/jak1/engine/camera/cam-debug-h.gc b/goal_src/jak1/engine/camera/cam-debug-h.gc index f3d7a42c42..b613ec7296 100644 --- a/goal_src/jak1/engine/camera/cam-debug-h.gc +++ b/goal_src/jak1/engine/camera/cam-debug-h.gc @@ -3,69 +3,42 @@ (bundles "ENGINE.CGO" "GAME.CGO") (require "kernel-defs.gc") -;; TODO - for misty-obs -(define-extern *camera-old-level* string) ;; unknown type +;; Previous state for engine performance comparisons +(define-extern *camera-old-level* string) +(define-extern *camera-old-cpu* int) +(define-extern *camera-old-vu* int) +(define-extern *camera-old-tfrag-bytes* int) +(define-extern *camera-old-stat-string-tfrag* string) +(define-extern *camera-old-stat-string-tfrag-near* string) +(define-extern *camera-old-stat-string-total* string) -(define-extern *camera-old-cpu* int) ;; unknown type - -(define-extern *camera-old-vu* int) ;; unknown type - -(define-extern *camera-old-tfrag-bytes* int) ;; unknown type - -(define-extern *camera-old-stat-string-tfrag* string) ;; unknown type - -(define-extern *camera-old-stat-string-tfrag-near* string) ;; unknown type - -(define-extern *camera-old-stat-string-total* string) ;; unknown type - -;; TODO - for cam-layout +;; Debug drawing functions for camera (define-extern camera-line-setup (function vector4w none)) - (define-extern camera-line-draw (function vector vector symbol)) - (define-extern camera-line (function vector vector vector4w none)) - (define-extern camera-cross (function vector vector vector vector4w meters basic)) - (define-extern camera-fov-frame (function matrix vector float float float vector4w none)) - -(define-extern cam-slave-options->string (function cam-slave-options object string)) - -(define-extern cam-index-options->string (function cam-index-options object string)) - -(define-extern debug-set-camera-pos-rot! (function vector matrix vector)) - -(define-extern camera-slave-debug (function camera-slave none)) - -;; TODO - for cam-states -(define-extern cam-debug-add-los-tri (function (inline-array collide-cache-tri) vector vector none)) - -(define-extern cam-collision-record-save (function vector vector int symbol camera-slave none)) - -(define-extern slave-los-state->string (function slave-los-state string)) - -(define-extern cam-debug-reset-coll-tri (function none)) ;; not confirmed - -;; TODO - for rolling-lightning-mole (define-extern camera-line-rel (function vector vector vector4w none)) -(declare-type clm basic) +(define-extern cam-slave-options->string (function cam-slave-options object string)) +(define-extern cam-index-options->string (function cam-index-options object string)) +(define-extern debug-set-camera-pos-rot! (function vector matrix vector)) +(define-extern camera-slave-debug (function camera-slave none)) +(define-extern cam-debug-add-los-tri (function (inline-array collide-cache-tri) vector vector none)) +(define-extern cam-collision-record-save (function vector vector int symbol camera-slave none)) +(define-extern slave-los-state->string (function slave-los-state string)) +(define-extern cam-debug-reset-coll-tri (function none)) -(define-extern *clm* clm) ;; unknown type -(define-extern *clm-edit* clm) - -(define-extern *clm-focalpull-attr* clm) ;; unknown type - -(define-extern *clm-index-attr* clm) ;; unknown type - -(define-extern *clm-intro-attr* clm) ;; unknown type - -(define-extern *clm-spline-attr* clm) ;; unknown type - -(define-extern *clm-vol-attr* clm) ;; unknown type - -(define-extern *clm-select* clm) ;; unknown type +; (declare-type clm basic) +; (define-extern *clm* clm) +; (define-extern *clm-edit* clm) +; (define-extern *clm-focalpull-attr* clm) +; (define-extern *clm-index-attr* clm) +; (define-extern *clm-intro-attr* clm) +; (define-extern *clm-spline-attr* clm) +; (define-extern *clm-vol-attr* clm) +; (define-extern *clm-select* clm) ;; DECOMP BEGINS diff --git a/goal_src/jak1/engine/camera/cam-debug.gc b/goal_src/jak1/engine/camera/cam-debug.gc index e8a242845a..58d7bbb468 100644 --- a/goal_src/jak1/engine/camera/cam-debug.gc +++ b/goal_src/jak1/engine/camera/cam-debug.gc @@ -55,26 +55,6 @@ :size-assert #x1d0 :flag-assert #x9000001d0) -(defmethod inspect ((obj cam-dbg-scratch)) - (format #t "[~8x] ~A~%" obj 'cam-dbg-scratch) - (format #t "~Tlinevec4w[2] @ #x~X~%" (-> obj linevec4w)) - (format #t "~Tcolor: ~`vector`P~%" (-> obj color)) - (format #t "~Tplotvec[2] @ #x~X~%" (-> obj plotvec)) - (format #t "~Tlinevec[2] @ #x~X~%" (-> obj linevec)) - (format #t "~Trel-vec: ~`vector`P~%" (-> obj rel-vec)) - (format #t "~Tsphere-v-start: ~`vector`P~%" (-> obj sphere-v-start)) - (format #t "~Tsphere-v-end: ~`vector`P~%" (-> obj sphere-v-end)) - (format #t "~Tsphere-v-down: ~`vector`P~%" (-> obj sphere-v-down)) - (format #t "~Tsphere-vec: ~`vector`P~%" (-> obj sphere-vec)) - (format #t "~Tcrossvec[3] @ #x~X~%" (-> obj crossvec)) - (format #t "~Tbboxvec[6] @ #x~X~%" (-> obj bboxvec)) - (format #t "~Tfov-vv[4] @ #x~X~%" (-> obj fov-vv)) - (format #t "~Tfov-src: ~`vector`P~%" (-> obj fov-src)) - (format #t "~Tfov-dest: ~`vector`P~%" (-> obj fov-dest)) - (format #t "~Tfov-vert: ~`vector`P~%" (-> obj fov-vert)) - (format #t "~Tfov-horz: ~`vector`P~%" (-> obj fov-horz)) - obj) - (defun cam-slave-options->string ((options cam-slave-options) (output object)) "Append the names of the enabled camera-slave option bits to output and return output as a string."