/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program 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 2 * of the License, or (at your option) any later version. * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "illusions/illusions.h" #include "illusions/camera.h" #include "illusions/fixedpoint.h" #include "illusions/resources/backgroundresource.h" #include "illusions/time.h" namespace Illusions { Camera::Camera(IllusionsEngine *vm) : _vm(vm) { init(); _activeState._cameraMode = 6; _activeState._paused = false; _activeState._panStartTime = getCurrentTime(); _activeState._panSpeed = 1; _activeState._bounds._topLeft.x = _screenMidX; _activeState._bounds._topLeft.y = _screenMidY; _activeState._bounds._bottomRight.x = _screenMidX; _activeState._bounds._bottomRight.y = _screenMidY; _activeState._currPan.x = _screenMidX; _activeState._currPan.y = _screenMidY; _activeState._panXShl = _screenMidX << 16; _activeState._panYShl = _screenMidY << 16; _activeState._panTargetPoint.x = _screenMidX; _activeState._panTargetPoint.y = 240; _activeState._panToPositionPtr = 0; _activeState._panNotifyId = 0; _activeState._trackingLimits.x = 0; _activeState._trackingLimits.y = 0; _activeState._centerPt.x = _screenMidX; _activeState._centerPt.y = _screenMidY; _activeState._pointFlags = 0; } void Camera::clearStack() { _stack.clear(); } void Camera::set(Common::Point &panPoint, WidthHeight &dimensions) { _activeState._cameraMode = 6; _activeState._paused = false; _activeState._panStartTime = getCurrentTime(); _activeState._panSpeed = 1; _activeState._bounds._topLeft.x = _screenMidX; _activeState._bounds._topLeft.y = _screenMidY; _activeState._bounds._bottomRight.x = MAX(0, dimensions._width - _screenWidth) + _screenMidX; _activeState._bounds._bottomRight.y = MAX(0, dimensions._height - _screenHeight) + _screenMidY; _activeState._panTargetPoint = panPoint; clipPanTargetPoint(); _activeState._currPan = _activeState._panTargetPoint; _activeState._panXShl = _activeState._currPan.x << 16; _activeState._panYShl = _activeState._currPan.y << 16; _vm->_backgroundInstances->refreshPan(); _activeState._panToPositionPtr = 0; _activeState._panObjectId = 0; _activeState._panNotifyId = 0; _activeState._trackingLimits.x = 0; _activeState._trackingLimits.y = 0; _activeState._pointFlags = 0; _activeState._centerPt.x = _screenMidX; _activeState._centerPt.y = _screenMidY; } void Camera::panCenterObject(uint32 objectId, int16 panSpeed) { Common::Point *actorPosition = _vm->getObjectActorPositionPtr(objectId); if (_vm->getGameId() == kGameIdDuckman && objectId == 0x40004) { _activeState._cameraMode = 2; _activeState._trackingLimits.x = 156; _activeState._trackingLimits.y = 96; } else if (_vm->getGameId() == kGameIdBBDOU) { _activeState._cameraMode = 1; _activeState._trackingLimits = _centerObjectTrackingLimits; } _activeState._panSpeed = panSpeed; _activeState._pointFlags = 0; _activeState._panObjectId = objectId; _activeState._panNotifyId = 0; _activeState._panToPositionPtr = actorPosition; _activeState._panTargetPoint = *actorPosition; clipPanTargetPoint(); _activeState._panStartTime = getCurrentTime(); recalcPan(_activeState._panStartTime); } void Camera::panTrackObject(uint32 objectId) { Common::Point *actorPosition = _vm->getObjectActorPositionPtr(objectId); _activeState._cameraMode = 3; _activeState._panObjectId = objectId; _activeState._trackingLimits = _trackObjectTrackingLimits; _activeState._panSpeed = _trackObjectTrackingLimitsPanSpeed; _activeState._pointFlags = 0; _activeState._panToPositionPtr = actorPosition; _activeState._panNotifyId = 0; _activeState._panTargetPoint = *actorPosition; clipPanTargetPoint(); _activeState._panStartTime = getCurrentTime(); recalcPan(_activeState._panStartTime); } void Camera::panToPoint(Common::Point pt, int16 panSpeed, uint32 panNotifyId) { _vm->notifyThreadId(_activeState._panNotifyId); _activeState._panTargetPoint = getPtOffset(pt); clipPanTargetPoint(); if (panSpeed) { _activeState._cameraMode = 5; _activeState._panSpeed = panSpeed; _activeState._trackingLimits.x = 0; _activeState._trackingLimits.y = 0; _activeState._pointFlags = 0; _activeState._panToPositionPtr = 0; _activeState._panNotifyId = panNotifyId; _activeState._panStartTime = getCurrentTime(); recalcPan(_activeState._panStartTime); } else { _activeState._currPan = _activeState._panTargetPoint; stopPan(); _vm->notifyThreadId(panNotifyId); } } void Camera::panEdgeFollow(uint32 objectId, int16 panSpeed) { Common::Point *actorPosition = _vm->getObjectActorPositionPtr(objectId); _activeState._cameraMode = 2; _activeState._trackingLimits.x = 318; _activeState._trackingLimits.y = 238; _activeState._pointFlags = 0; _activeState._panSpeed = panSpeed; _activeState._panToPositionPtr = actorPosition; _activeState._panObjectId = objectId; _activeState._panTargetPoint = _activeState._currPan; _activeState._panNotifyId = 0; clipPanTargetPoint(); _activeState._panStartTime = getCurrentTime(); recalcPan(_activeState._panStartTime); } void Camera::stopPan() { _activeState._cameraMode = 6; _activeState._panTargetPoint = _activeState._currPan; _activeState._panSpeed = 1; _activeState._panXShl = _activeState._currPan.x << 16; _activeState._panYShl = _activeState._currPan.y << 16; _activeState._panToPositionPtr = 0; _activeState._panObjectId = 0; _activeState._panNotifyId = 0; _activeState._pointFlags = 0; _vm->_backgroundInstances->refreshPan(); } void Camera::pause() { _activeState._pauseStartTime = getCurrentTime(); _activeState._paused = true; } void Camera::unpause() { _activeState._paused = false; uint32 pauseDuration = getCurrentTime() - _activeState._pauseStartTime; _activeState._time28 += pauseDuration; _activeState._panStartTime += pauseDuration; } void Camera::pushCameraMode() { CameraModeStackItem item; item._cameraMode = _activeState._cameraMode; item._panSpeed = _activeState._panSpeed; item._panObjectId = 0; item._panNotifyId = 0; item._panTargetPoint.x = 0; item._panTargetPoint.y = 0; switch (_activeState._cameraMode) { case 1: case 2: case 3: item._panObjectId = _activeState._panObjectId; break; case 4: item._cameraMode = 3; item._panObjectId = _activeState._panObjectId; break; case 5: item._panTargetPoint = _activeState._panTargetPoint; item._panNotifyId = _activeState._panNotifyId; break; } _stack.push(item); } void Camera::popCameraMode() { if (_stack.empty()) return; CameraModeStackItem item = _stack.pop(); if (item._panObjectId && !_vm->getObjectActorPositionPtr(item._panObjectId)) { // Tracking object doesn't exist any longer stopPan(); return; } switch (item._cameraMode) { case 1: panCenterObject(item._panObjectId, item._panSpeed); break; case 2: panEdgeFollow(item._panObjectId, item._panSpeed); break; case 3: panTrackObject(item._panObjectId); break; case 5: panToPoint(item._panTargetPoint, item._panSpeed, item._panNotifyId); break; case 6: stopPan(); break; } } void Camera::clearCameraModeStack() { _stack.clear(); } void Camera::update(uint32 currTime) { if (_activeState._paused) return; switch (_activeState._cameraMode) { case 1: updateMode1(currTime); break; case 2: updateMode2(currTime); break; case 3: updateMode3(currTime); break; } if (_activeState._cameraMode != 6) { if (!isPanFinished() && updatePan(currTime)) { /* Unused if (_activeState._cameraMode == 1 || _activeState._cameraMode == 5) nullsub_2(); */ _vm->_backgroundInstances->refreshPan(); } if (isPanFinished()) { if (_activeState._cameraMode == 5) { // Notify a thread that the camera panning has finished _vm->notifyThreadId(_activeState._panNotifyId); _activeState._cameraMode = 6; } else if (_activeState._cameraMode == 4) { _activeState._cameraMode = 3; } } } } void Camera::setBounds(Common::Point minPt, Common::Point maxPt) { _activeState._bounds._topLeft = minPt; _activeState._bounds._bottomRight = maxPt; } void Camera::setBoundsToDimensions(WidthHeight &dimensions) { // NOTE For the caller dimensions = artdispGetMasterBGDimensions(); _activeState._bounds._topLeft.x = _screenMidX; _activeState._bounds._topLeft.y = _screenMidY; _activeState._bounds._bottomRight.x = MAX(0, dimensions._width - _screenWidth) + _screenMidX; _activeState._bounds._bottomRight.y = MAX(0, dimensions._height - _screenHeight) + _screenMidY; clipPanTargetPoint(); } Common::Point Camera::getCurrentPan() { return _activeState._currPan; } Common::Point Camera::getScreenOffset() { Common::Point screenOffs = getCurrentPan(); screenOffs.x -= _screenMidX; screenOffs.y -= _screenMidY; return screenOffs; } Common::Point Camera::getTrackingLimits() { return _activeState._trackingLimits; } bool Camera::isAtPanLimit(int limitNum) { switch (limitNum) { case 1: return _activeState._currPan.y <= _activeState._bounds._topLeft.y; case 2: return _activeState._currPan.y >= _activeState._bounds._bottomRight.y; case 3: return _activeState._currPan.x <= _activeState._bounds._topLeft.x; case 4: return _activeState._currPan.x >= _activeState._bounds._bottomRight.x; } return false; } void Camera::setActiveState(CameraState &state) { _activeState = state; _activeState._panStartTime = getCurrentTime(); } void Camera::getActiveState(CameraState &state) { state = _activeState; } void Camera::refreshPan(BackgroundInstance *backgroundItem, WidthHeight &dimensions) { Common::Point screenOffs = getScreenOffset(); int x = dimensions._width - _screenWidth; int y = dimensions._height - _screenHeight; for (uint i = 0; i < backgroundItem->_bgRes->_bgInfosCount; ++i) { const BgInfo &bgInfo = backgroundItem->_bgRes->_bgInfos[i]; if (bgInfo._flags & 1) { backgroundItem->_panPoints[i] = screenOffs; } else { Common::Point newOffs(0, 0); if (x > 0 && bgInfo._surfInfo._dimensions._width - _screenWidth > 0) newOffs.x = screenOffs.x * (bgInfo._surfInfo._dimensions._width - _screenWidth) / x; if (y > 0 && bgInfo._surfInfo._dimensions._height - _screenHeight > 0) newOffs.y = screenOffs.y * (bgInfo._surfInfo._dimensions._height - _screenHeight) / y; backgroundItem->_panPoints[i] = newOffs; } } } void Camera::updateMode1(uint32 currTime) { Common::Point ptOffs = getPtOffset(*_activeState._panToPositionPtr); int deltaX = ptOffs.x - _activeState._currPan.x + _screenMidX - _activeState._centerPt.x; int deltaY = ptOffs.y - _activeState._currPan.y + _screenMidY - _activeState._centerPt.y; int deltaXAbs = ABS(deltaX); int deltaYAbs = ABS(deltaY); if (deltaXAbs > _activeState._trackingLimits.x) _activeState._panTargetPoint.x = _activeState._currPan.x + ABS(deltaXAbs - _activeState._trackingLimits.x) * (deltaX >= 0 ? 1 : -1); else _activeState._panTargetPoint.x = _activeState._currPan.x; if (deltaYAbs > _activeState._trackingLimits.y) _activeState._panTargetPoint.y = _activeState._currPan.y + ABS(deltaYAbs - _activeState._trackingLimits.y) * (deltaY >= 0 ? 1 : -1); else _activeState._panTargetPoint.y = _activeState._currPan.y; clipPanTargetPoint(); if (!isPanFinished()) { uint32 oldPanTime = _activeState._panStartTime; _activeState._panStartTime = _activeState._time28; recalcPan(oldPanTime); } } void Camera::updateMode2(uint32 currTime) { // TOOD CHECKME Bigger differences in Duckman Common::Point panToPosition = *_activeState._panToPositionPtr; uint pointFlags = 0; WRect rect; rect._topLeft.x = _screenMidX - _activeState._trackingLimits.x; rect._topLeft.y = _screenMidY - _activeState._trackingLimits.y; rect._bottomRight.x = _screenMidX + _activeState._trackingLimits.x; rect._bottomRight.y = _screenMidY + _activeState._trackingLimits.y; if (calcPointFlags(panToPosition, rect, pointFlags)) { if (pointFlags != _activeState._pointFlags) { _activeState._pointFlags = pointFlags; if (pointFlags & 1) _activeState._panTargetPoint.x = _activeState._bounds._topLeft.x; else if (pointFlags & 2) _activeState._panTargetPoint.x = _activeState._bounds._bottomRight.x; else _activeState._panTargetPoint.x = _activeState._currPan.x; if (pointFlags & 4) _activeState._panTargetPoint.y = _activeState._bounds._topLeft.y; else if (pointFlags & 8) _activeState._panTargetPoint.y = _activeState._bounds._bottomRight.y; else _activeState._panTargetPoint.y = _activeState._currPan.y; clipPanTargetPoint(); _activeState._panStartTime = currTime; recalcPan(currTime); } } else if (_activeState._pointFlags) { _activeState._pointFlags = 0; _activeState._panTargetPoint = _activeState._currPan; } } void Camera::updateMode3(uint32 currTime) { Common::Point panToPosition = *_activeState._panToPositionPtr; int deltaX = panToPosition.x - _activeState._currPan.x; int deltaY = panToPosition.y - _activeState._currPan.y; if (ABS(deltaX) > _activeState._trackingLimits.x) _activeState._panTargetPoint.x = _activeState._currPan.x + 2 * _activeState._trackingLimits.x * (deltaX >= 0 ? 1 : -1); else _activeState._panTargetPoint.x = _activeState._currPan.x; if (ABS(deltaY) > _activeState._trackingLimits.y) _activeState._panTargetPoint.y = _activeState._currPan.y + 2 * _activeState._trackingLimits.y * (deltaY >= 0 ? 1 : -1); else _activeState._panTargetPoint.y = _activeState._currPan.y; clipPanTargetPoint(); if (!isPanFinished()) { _activeState._panStartTime = currTime; recalcPan(currTime); _activeState._cameraMode = 4; } } bool Camera::updatePan(uint32 currTime) { if (currTime - _activeState._time28 >= _activeState._time2E) { _activeState._panXShl = _activeState._panTargetPoint.x << 16; _activeState._panYShl = _activeState._panTargetPoint.y << 16; } else { _activeState._panXShl += fixedMul(_activeState._someX, (currTime - _activeState._panStartTime) << 16); _activeState._panYShl += fixedMul(_activeState._someY, (currTime - _activeState._panStartTime) << 16); } _activeState._panStartTime = currTime; Common::Point newPan(_activeState._panXShl >> 16, _activeState._panYShl >> 16); if (_activeState._currPan.x != newPan.x || _activeState._currPan.y != newPan.y) { _activeState._currPan = newPan; return true; } return false; } bool Camera::isPanFinished() { return _activeState._currPan.x == _activeState._panTargetPoint.x && _activeState._currPan.y == _activeState._panTargetPoint.y; } Common::Point Camera::getPtOffset(Common::Point pt) { pt.x = pt.x - _activeState._centerPt.x + _screenMidX; pt.y = pt.y - _activeState._centerPt.y + _screenMidY; return pt; } void Camera::recalcPan(uint32 currTime) { _activeState._currPan2 = getCurrentPan(); _activeState._time28 = currTime; if (_activeState._panSpeed == 0) { _activeState._time2E = 0; } else { FP16 x1 = _activeState._currPan2.x << 16; FP16 y1 = _activeState._currPan2.y << 16; FP16 x2 = _activeState._panTargetPoint.x << 16; FP16 y2 = _activeState._panTargetPoint.y << 16; FP16 distance = fixedDistance(x1, y1, x2, y2); _activeState._time2E = 60 * fixedTrunc(distance) / _activeState._panSpeed; } if (_activeState._time2E != 0) { _activeState._someX = fixedDiv((_activeState._panTargetPoint.x - _activeState._currPan2.x) << 16, _activeState._time2E << 16); _activeState._someY = fixedDiv((_activeState._panTargetPoint.y - _activeState._currPan2.y) << 16, _activeState._time2E << 16); } else { _activeState._someX = (_activeState._panTargetPoint.x - _activeState._currPan2.x) << 16; _activeState._someY = (_activeState._panTargetPoint.y - _activeState._currPan2.y) << 16; } } bool Camera::calcPointFlags(Common::Point &pt, WRect &rect, uint &outFlags) { bool result = false; if (pt.x < rect._topLeft.x) { outFlags |= 1; result = true; } else if (pt.x > rect._bottomRight.x) { outFlags |= 2; result = true; } if (pt.y < rect._topLeft.y) { outFlags |= 4; result = true; } else if (pt.y > rect._bottomRight.y) { outFlags |= 8; result = true; } return result; } void Camera::clipPanTargetPoint() { _activeState._panTargetPoint.x = CLIP(_activeState._panTargetPoint.x, _activeState._bounds._topLeft.x, _activeState._bounds._bottomRight.x); _activeState._panTargetPoint.y = CLIP(_activeState._panTargetPoint.y, _activeState._bounds._topLeft.y, _activeState._bounds._bottomRight.y); } void Camera::init() { switch (_vm->getGameId()) { case kGameIdDuckman: initDuckman(); break; case kGameIdBBDOU: initBBDOU(); break; } } void Camera::initDuckman() { _centerObjectTrackingLimits.x = 4; _centerObjectTrackingLimits.y = 4; _screenWidth = 320; _screenHeight = 200; _screenMidX = 160; _screenMidY = 100; _trackObjectTrackingLimits.x = 80; _trackObjectTrackingLimits.y = 50; _trackObjectTrackingLimitsPanSpeed = 353; } void Camera::initBBDOU() { _centerObjectTrackingLimits.x = 8; _centerObjectTrackingLimits.y = 8; _screenWidth = 640; _screenHeight = 480; _screenMidX = 320; _screenMidY = 240; _trackObjectTrackingLimits.x = 160; _trackObjectTrackingLimits.y = 120; _trackObjectTrackingLimitsPanSpeed = 710; } } // End of namespace Illusions