/* * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * * Antkeeper Source Code is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Antkeeper Source Code is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Antkeeper Source Code. If not, see . */ #include "forceps.hpp" #include "camera-rig.hpp" #include Forceps::Forceps(const Model* model, Animator* animator): wasActive(false) { // Allocate pose and initialize to bind pose pose = new Pose(model->getSkeleton()); pose->reset(); // Setup model instance modelInstance.setModel(model); modelInstance.setPose(pose); // Find pinch animation pinchClip = model->getSkeleton()->getAnimationClip("pinch"); if (!pinchClip) { throw std::runtime_error("Forceps pinch animation clip not found."); } // Find release animation releaseClip = model->getSkeleton()->getAnimationClip("release"); if (!releaseClip) { throw std::runtime_error("Forceps release animation clip not found."); } // Scale animation speed float pinchDuration = 0.1f; float releaseDuration = 0.05f; float pinchSpeed = std::get<1>(pinchClip->getTimeFrame()) / pinchDuration; float releaseSpeed = std::get<1>(releaseClip->getTimeFrame()) / releaseDuration; std::cout << std::get<1>(pinchClip->getTimeFrame()) << std::endl; std::cout << std::get<1>(releaseClip->getTimeFrame()) << std::endl; // Setup pinch animation callbacks pinchAnimation.setSpeed(pinchSpeed); pinchAnimation.setTimeFrame(pinchClip->getTimeFrame()); pinchAnimation.setClip(pinchClip); pinchAnimation.setAnimateCallback ( [this](std::size_t id, const Transform& transform) { this->pose->setRelativeTransform(id, transform); } ); pinchAnimation.setEndCallback ( [this]() { this->pinched = true; } ); // Setup release animation callbacks releaseAnimation.setSpeed(releaseSpeed); releaseAnimation.setTimeFrame(releaseClip->getTimeFrame()); releaseAnimation.setClip(releaseClip); releaseAnimation.setAnimateCallback ( [this](std::size_t id, const Transform& transform) { this->pose->setRelativeTransform(id, transform); } ); hoverDistance = 1.0f; // Setup timing float descentDuration = 0.125f; float ascentDuration = 0.125f; /* // Allocate tweener and and setup tweens tweener = new Tweener(); descentTween = new Tween(EaseFunction::OUT_CUBIC, 0.0f, descentDuration, hoverDistance, -hoverDistance); ascentTween = new Tween(EaseFunction::IN_CUBIC, 0.0f, ascentDuration, 0.0f, hoverDistance); descentTween->setEndCallback(std::bind(&TweenBase::start, ascentTween)); tweener->addTween(descentTween); tweener->addTween(ascentTween); */ // Setup initial state state = Forceps::State::RELEASED; for (std::size_t i = 0; i < pinchClip->getChannelCount(); ++i) { const AnimationChannel* channel = pinchClip->getChannelByIndex(i); pose->setRelativeTransform(channel->getChannelID(), *std::get<1>(channel->getKeyframe(0))); } pose->concatenate(); animator->addAnimation(&pinchAnimation); animator->addAnimation(&releaseAnimation); } Forceps::~Forceps() { delete pose; } void Forceps::update(float dt) { // Determine distance from pick point float forcepsDistance = hoverDistance; Quaternion alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); Quaternion tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); tilt = tilt * glm::angleAxis(glm::radians(-70.0f), tilt * Vector3(0, 1, 0)); Quaternion rotation = glm::normalize(alignment * tilt); Vector3 translation = pick + rotation * Vector3(0, forcepsDistance, 0); // Set tool position modelInstance.setTranslation(translation); modelInstance.setRotation(rotation); pose->concatenate(); if (active && !wasActive) { modelInstance.resetTweens(); modelInstance.setActive(true); } else if (!active && wasActive) { modelInstance.setActive(false); } wasActive = active; /* if (state == Forceps::State::RELEASED) { } else if (state == Forceps::State::RELEASING) { // Perform release animation releaseAnimation->animate(pose, animationTime); pose->concatenate(); // If release animation is finished if (animationTime >= releaseAnimation->getEndTime()) { // Changed to released state state = Forceps::State::RELEASED; } } else if (state == Forceps::State::PINCHED) { if (!ascentTween->isStopped()) { // Calculate interpolation factor float interpolationFactor = (ascentTween->getTweenValue() - ascentTween->getStartValue()) / ascentTween->getDeltaValue(); // Form tilt quaternion //Quaternion tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); // Project camera forward onto XZ plane Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); cameraForwardXZ.y = 0.0f; cameraForwardXZ = glm::normalize(cameraForwardXZ); // Form alignment quaternion //Quaternion alignment = glm::rotation(Vector3(0, 0, -1), cameraForwardXZ); alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); // Calculate target rotation at the top of the ascentTween rotationTop = glm::normalize(alignment * tilt); // Interpolate between bottom and top rotations Quaternion interpolatedRotation = glm::normalize(glm::slerp(rotationBottom, rotationTop, interpolationFactor)); // Set target translation at the top of the ascent translationTop = pick + rotationTop * Vector3(0, hoverDistance, 0); // Interpolate between bottom and top translations Vector3 interpolatedTranslation = glm::lerp(translationBottom, translationTop, interpolationFactor); // Update model instance transform modelInstance.setTranslation(interpolatedTranslation); modelInstance.setRotation(interpolatedRotation); } if (suspendedAnt != nullptr) { // Project forceps forward vector onto XZ plane Vector3 forward = glm::normalize(modelInstance.getRotation() * Vector3(0, 0, -1)); forward.y = 0.0f; forward = glm::normalize(forward); // Calculate suspension quaternion Quaternion suspensionRotation = glm::normalize(glm::rotation(Vector3(0, 0, -1), ((flipRotation) ? -forward : forward))); // Suspend ant suspendedAnt->suspend(modelInstance.getTranslation(), suspensionRotation); } } else if (state == Forceps::State::PINCHING) { // Perform pinch animation pinchAnimation->animate(pose, animationTime); pose->concatenate(); // Rotate to align forceps with ant if (targetedAnt != nullptr) { // Calculate interpolation factor float interpolationFactor = (descentTween->getTweenValue() - descentTween->getStartValue()) / descentTween->getDeltaValue(); // Set target translation at the bottom of the descent translationBottom = targetedAnt->getPosition(); // Interpolate between top and bottom translations Vector3 interpolatedTranslation = glm::lerp(translationTop, translationBottom, interpolationFactor); // Project camera forward onto XZ plane Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); cameraForwardXZ.y = 0.0f; cameraForwardXZ = glm::normalize(cameraForwardXZ); // Form tilt quaternion tilt = glm::angleAxis(glm::radians(15.0f), -cameraForwardXZ); // Project ant forward onto XZ plane Vector3 antForwardXZ = targetedAnt->getForward(); antForwardXZ.y = 0.0f; antForwardXZ = glm::normalize(antForwardXZ); // Form alignment quaternion alignment = glm::rotation(Vector3(0, 0, -1), (flipRotation) ? antForwardXZ : -antForwardXZ); // Calculate target rotation at the bottom of the descent rotationBottom = glm::normalize(tilt * alignment); // Interpolate between top and bottom rotations Quaternion interpolatedRotation = glm::normalize(glm::slerp(rotationTop, rotationBottom, interpolationFactor)); // Update model instance transform modelInstance.setTranslation(interpolatedTranslation); modelInstance.setRotation(interpolatedRotation); } // If pinch animation is finished if (animationTime >= pinchAnimation->getEndTime() && descentTween->isStopped()) { // If an ant was targeted if (targetedAnt != nullptr) { // Suspend targeted ant suspendedAnt = targetedAnt; suspendedAnt->setState(Ant::State::SUSPENDED); //suspendedAnt->suspend(modelInstance.getTranslation()); targetedAnt = nullptr; } // Change to pinched state state = Forceps::State::PINCHED; } } */ } void Forceps::pinch() { releaseAnimation.stop(); pinchAnimation.rewind(); pinchAnimation.play(); /* // Change state to pinching state = Forceps::State::PINCHING; animationTime = 0.0f; if (colony != nullptr) { // Target nearest ant in pinching radius Sphere pinchingBounds = Sphere(pick, 0.35f); // Build a list of ants which intersect the pinching bounds std::list ants; colony->queryAnts(pinchingBounds, &ants); // Target ant closest to the center of the pinching bounds float closestDistance = std::numeric_limits::infinity(); for (Agent* agent: ants) { Ant* ant = static_cast(agent); Vector3 difference = ant->getPosition() - pinchingBounds.getCenter(); float distanceSquared = glm::dot(difference, difference); if (distanceSquared < closestDistance) { closestDistance = distanceSquared; targetedAnt = ant; } } if (targetedAnt != nullptr) { // Start descent tweener descentTween->start(); // Save translation & rotation translationTop = modelInstance.getTranslation(); rotationTop = modelInstance.getRotation(); // Project ant forward onto XZ plane Vector3 antForwardXZ = targetedAnt->getForward(); antForwardXZ.y = 0.0f; antForwardXZ = glm::normalize(antForwardXZ); // Project camera forward onto XZ plane Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); cameraForwardXZ.y = 0.0f; cameraForwardXZ = glm::normalize(cameraForwardXZ); // Find angle between ant and camera on XZ plane float angle = std::acos(glm::dot(cameraForwardXZ, antForwardXZ)); // Determine direction to rotate flipRotation = (angle > glm::radians(90.0f)); } } */ } void Forceps::release() { pinchAnimation.stop(); releaseAnimation.rewind(); releaseAnimation.play(); /* // Change state to releasing state = Forceps::State::RELEASING; animationTime = 0.0f; targetedAnt = nullptr; if (suspendedAnt != nullptr) { Ray pickingRay; pickingRay.origin = pick + Vector3(0, 1, 0); pickingRay.direction = Vector3(0, -1, 0); const std::vector* navmeshTriangles = navmesh->getTriangles(); for (Navmesh::Triangle* triangle: *navmeshTriangles) { auto result = intersects(pickingRay, triangle); if (std::get<0>(result)) { Vector3 barycentricPosition = Vector3(std::get<2>(result), std::get<3>(result), 1.0f - std::get<2>(result) - std::get<3>(result)); suspendedAnt->setPosition(triangle, barycentricPosition); break; } } // Release suspended ant suspendedAnt->setState(Ant::State::WANDER); suspendedAnt = nullptr; } // Reset tweens descentTween->reset(); descentTween->stop(); ascentTween->reset(); ascentTween->stop(); */ }