// // ToyColourableSprite.cpp // SteveMaggieCpp // // Created by Katarzyna Kalinowska-Górska on 17.05.2017. // // #include #include "cocos2d.h" #include "ToyColourableSprite.h" #include "ToyJSONParseUtils.h" #include "ToyGeometryUtils.h" #include "ToyDrawingUtils.h" #include "ToyActionParser.h" #include "ToyScalingUtils.h" ToyColourableSprite* ToyColourableSprite::createWithSpritePaths(std::string whiteSpritePath, std::string revealedSpritePath) { ToyColourableSprite * sprite = new (std::nothrow) ToyColourableSprite(); if(sprite && sprite->initWithSpritePaths(whiteSpritePath, revealedSpritePath)) { sprite->autorelease(); return sprite; } CC_SAFE_DELETE(sprite); return nullptr; } bool ToyColourableSprite::initWithSpritePaths(std::string whiteSpritePath, std::string revealedSpritePath) { if (!ToyPlainNode::init()) { return false; } _flashable = false; _whiteSpriteDisappears = false; _colouringEnabled = false; _autofilled = false; _playingDemoAnimation = false; _deferringRepaint = false; _autofilling = false; _brushThickness = defaultBrushThickness; _whiteSpriteFilePath = whiteSpritePath; _revealedSpriteFilePath = revealedSpritePath; _autofillRects.clear(); _autofillCallback = [](){}; _renderTextureWhite = NULL; _renderTextureRevealed = NULL; _drawNodeWhite = NULL; _drawNodeRevealed = NULL; _rememberedTouchLocations.clear(); _lastBrushIntersection = cocos2d::Rect(0,0,0,0); _brush = NULL; _whiteSprite = cocos2d::Sprite::create(_whiteSpriteFilePath); _revealedSprite = cocos2d::Sprite::create(_revealedSpriteFilePath); _flashingSprite = NULL; _drawingWithFinger = false; return true; } ToyColourableSprite::~ToyColourableSprite(){ if(_drawNodeWhite != nullptr){ _drawNodeWhite->release(); } if(_drawNodeRevealed != nullptr){ _drawNodeRevealed->release(); } if(_whiteSprite != nullptr){ _whiteSprite->release(); } if(_revealedSprite != nullptr){ _revealedSprite->release(); } } void ToyColourableSprite::loadPropertiesFromJSON(const rapidjson::Value& jsonValue, ToyLayoutViewInterface* scene, const std::string resFolder, const std::string altResFolder) { ToyPlainNode::loadPropertiesFromJSON(jsonValue, scene, resFolder, altResFolder); // this->loadCommonPropertiesFromJSON(jsonValue); if (ToyJSONParseUtils::hasMemberFloat(jsonValue, "brushThickness")){ _brushThickness = jsonValue["brushThickness"].GetFloat(); } if (ToyJSONParseUtils::checkMemberBool(jsonValue, "flashable", true)) { _flashable = true; } if (ToyJSONParseUtils::hasMemberArray(jsonValue, "autofillRects")) { const auto& autofillRects = jsonValue["autofillRects"].GetArray(); for (const auto& rect : autofillRects) { auto cocosRect = cocos2d::Rect(rect["x"].GetFloat()*ToyScalingUtils::scaleAspectFitToDesignIpadProSize(), rect["y"].GetFloat()*ToyScalingUtils::scaleAspectFitToDesignIpadProSize(), rect["w"].GetFloat()*ToyScalingUtils::scaleAspectFitToDesignIpadProSize(), rect["h"].GetFloat()*ToyScalingUtils::scaleAspectFitToDesignIpadProSize()); AutofillRect newRect = AutofillRect(cocosRect, false); _autofillRects.push_back(newRect); } } } void ToyColourableSprite::prepareSize(const rapidjson::Value& jsonValue, float& width, float& height) { width = this->_whiteSprite->getBoundingBox().size.width; height = this->_revealedSprite->getBoundingBox().size.height; } // TODO: it is now assumed that white sprite and revealed sprite have the same size void ToyColourableSprite::onEnter() { ToyPlainNode::onEnter(); if (_whiteSprite != nullptr && _revealedSprite != nullptr) { _drawNodeWhite = cocos2d::DrawNode::create(); _drawNodeWhite->retain(); //TODO: releasing? memory leaks? _drawNodeRevealed = cocos2d::DrawNode::create(); _drawNodeRevealed->retain(); //TODO: releasing? memory leaks? auto size = cocos2d::Size(_revealedSprite->getBoundingBox().size.width, _revealedSprite->getBoundingBox().size.height); this->setContentSize(size); _renderTextureWhite = cocos2d::RenderTexture::create(size.width, size.height, cocos2d::backend::PixelFormat::RGBA8888, cocos2d::backend::PixelFormat::D24S8); _renderTextureRevealed = cocos2d::RenderTexture::create(size.width, size.height, cocos2d::backend::PixelFormat::RGBA8888, cocos2d::backend::PixelFormat::D24S8); this->addChild(_renderTextureWhite, 0); this->addChild(_renderTextureRevealed, 1); _whiteSprite->setPosition(size.width/2, size.height/2); _whiteSprite->retain(); _renderTextureWhite->setPosition(_whiteSprite->getPosition()); _renderTextureRevealed->setPosition(_whiteSprite->getPosition()); _revealedSprite->setPosition(_whiteSprite->getPosition()); _revealedSprite->retain(); //retain maybe safer to call ctor!! _whiteSprite->setBlendFunc(cocos2d::BlendFunc::DISABLE); // ONE, ZERO, first: source - incoming, second: destination - already on screen // cocos2d::BlendFunc whiteBlendFunc; // whiteBlendFunc.src = cocos2d::backend::BlendFactor::ZERO; // // if(_whiteSpriteDisappears){ // whiteBlendFunc.dst = cocos2d::backend::BlendFactor::ONE_MINUS_SRC_ALPHA; // } else { // whiteBlendFunc.dst = cocos2d::backend::BlendFactor::ONE; // } // // _drawNodeWhite->setBlendFunc(whiteBlendFunc); cocos2d::BlendFunc whiteDrawNodeBlendFunc; whiteDrawNodeBlendFunc.dst = cocos2d::backend::BlendFactor::ZERO; whiteDrawNodeBlendFunc.src = cocos2d::backend::BlendFactor::ONE; _drawNodeWhite->setBlendFunc(whiteDrawNodeBlendFunc); // _drawNodeWhite->setBlendFunc(cocos2d::BlendFunc::DISABLE); // cocos2d::BlendFunc whiteBlendFunc; // //TODO // // if(_whiteSpriteDisappears){ // // whiteBlendFunc.dst = cocos2d::backend::BlendFactor::ONE_MINUS_SRC_ALPHA; // // } else { // whiteBlendFunc.dst = cocos2d::backend::BlendFactor::ZERO; // // } // whiteBlendFunc.src = cocos2d::backend::BlendFactor::ONE_MINUS_DST_ALPHA; // _whiteSprite->setBlendFunc(whiteBlendFunc); cocos2d::BlendFunc revealedBlendFunc; revealedBlendFunc.src = cocos2d::backend::BlendFactor::ONE; revealedBlendFunc.dst = cocos2d::backend::BlendFactor::ONE; _drawNodeRevealed->setBlendFunc(revealedBlendFunc); cocos2d::BlendFunc revealedSpriteBlendFunc; revealedSpriteBlendFunc.src = cocos2d::backend::BlendFactor::DST_ALPHA; revealedSpriteBlendFunc.dst = cocos2d::backend::BlendFactor::ZERO; _revealedSprite->setBlendFunc(revealedSpriteBlendFunc); this->clear(); if (_flashable) { _flashingSprite = cocos2d::Sprite::create(_revealedSpriteFilePath); this->addChild(_flashingSprite); _flashingSprite->setPosition(_whiteSprite->getPosition()); _flashingSprite->setOpacity(0); } //debug code show utoill rects // if(_autofillRects.size() > 0){ // // _renderTextureRevealed->begin(); // // for(int i = 0; i < _autofillRects.size(); ++i){ // auto rect = _autofillRects[i]; // _drawNodeRevealed->drawSolidRect(cocos2d::Point(rect.rect.origin.x, rect.rect.origin.y), cocos2d::Point(rect.rect.origin.x + rect.rect.size.width, rect.rect.origin.y+rect.rect.size.height), cocos2d::Color4F(1.0f, 1.0f, 1.0f, 1.0f)); // } // // _drawNodeRevealed->Node::visit(); // _revealedSprite->visit(); // _renderTextureRevealed->end(); // // } ///////// this->configureTouchListeners(); } } void ToyColourableSprite::configureTouchListeners() { auto touchListener = cocos2d::EventListenerTouchOneByOne::create(); touchListener->onTouchBegan = [&](cocos2d::Touch* touch, cocos2d::Event* event) -> bool{ auto touchLocation = touch->getLocation(); if(this->_colouringEnabled && ToyGeometryUtils::getBoundingBoxToWorld(this).containsPoint(touchLocation)){ auto touchLocation = touch->getLocation(); if(ToyGeometryUtils::getBoundingBoxToWorld(this).containsPoint(touchLocation)) { _drawingWithFinger = true; auto convertedTouchLocation = this->convertToNodeSpace(touch->getLocation()); _previousTouchPoint = convertedTouchLocation; _currentTouchPoint = convertedTouchLocation; this->scheduleUpdate(); } return true; } else if(this->_brush != NULL) { return true; } return false; }; touchListener->onTouchMoved = [&](cocos2d::Touch* touch, cocos2d::Event* event){ if(this->_brush != nullptr){ auto brushPaintingPart = _brush->paintingRect(); auto worldPaintingPartPosition = _brush->convertToWorldSpace(brushPaintingPart.origin); brushPaintingPart = cocos2d::Rect(worldPaintingPartPosition, brushPaintingPart.size); if (ToyGeometryUtils::getBoundingBoxToWorld(this).intersectsRect(brushPaintingPart)) {//TODO maybe ignore scale = false this->paintWithBrush(_brush, true); } else { _lastBrushIntersection = cocos2d::Rect(0,0,0,0); } } else if(_drawingWithFinger){//this->_colouringEnabled){ _currentTouchPoint = this->convertToNodeSpace(touch->getLocation()); } }; touchListener->onTouchEnded = [&](cocos2d::Touch* touch, cocos2d::Event* event){ if(_drawingWithFinger){ _drawingWithFinger = false; this->unscheduleUpdate(); } if(_colouringEnabled){ if(!this->drawLinesOnTouch(touch)){ auto currentLocation = this->convertToNodeSpace(touch->getLocation()); this->drawOnTextures([&](cocos2d::DrawNode* drawNode){ drawNode->drawSolidCircle(currentLocation, _brushThickness, 2*M_PI, 16, cocos2d::Color4F(1,1,1,1)); }, false); this->markAutofillRectanglesByPoint(currentLocation); } } if(!_autofilled && this->checkAutofillRectangles()){ this->autofill(); } }; _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this); } bool ToyColourableSprite::drawLinesOnTouch(cocos2d::Touch* touch) { auto currentLocation = this->convertToNodeSpace(touch->getLocation()); auto prevLocation = this->convertToNodeSpace(touch->getPreviousLocation()); if(currentLocation == prevLocation){ return false; } this->drawLine(prevLocation, currentLocation, _brushThickness); this->markAutofillRectangles(prevLocation, currentLocation); return true; } void ToyColourableSprite::startUsingBrush(ToyDraggableBrushSprite* brush) { _brush = brush; _lastBrushIntersection = cocos2d::Rect(0,0,0,0); } void ToyColourableSprite::endUsingBrush() { _brush = NULL; } void ToyColourableSprite::paintWithBrush(ToyDraggableBrushSprite* brush, bool usePreviousBrushLocation) { auto brushPaintingPart = brush->paintingRect(); auto worldPaintingPartPosition = _brush->convertToWorldSpace(brushPaintingPart.origin); brushPaintingPart = cocos2d::Rect(worldPaintingPartPosition, brushPaintingPart.size); auto thisBBToworld = ToyGeometryUtils::getBoundingBoxToWorld(this); if((_colouringEnabled || _playingDemoAnimation) && thisBBToworld.intersectsRect(brushPaintingPart)){ auto intersection = ToyGeometryUtils::getRectIntersection(thisBBToworld, brushPaintingPart); if(usePreviousBrushLocation){ auto lines = _brush->getSegmentsToPaint(_lastBrushIntersection, intersection); if(lines.size() == 3){ auto point11 = this->convertToNodeSpace(lines[0].point1); auto point21 = this->convertToNodeSpace(lines[0].point2); auto point12 = this->convertToNodeSpace(lines[1].point1); auto point22 = this->convertToNodeSpace(lines[1].point2); auto point13 = this->convertToNodeSpace(lines[2].point1); auto point23 = this->convertToNodeSpace(lines[2].point2); _deferringRepaint = true; this->drawLine(point11, point21, lines[0].thickness); this->drawLine(point12, point22, lines[1].thickness); _deferringRepaint = false; this->drawLine(point13, point23, lines[2].thickness); this->markAutofillRectangles(point11, point21); this->markAutofillRectangles(point12, point22); this->markAutofillRectangles(point13, point23); this->markAutofillRectangles(point11, point13); this->markAutofillRectangles(point21, point23); this->markAutofillRectanglesByRect(cocos2d::Rect(this->convertToNodeSpace(brushPaintingPart.origin), brushPaintingPart.size)); //TODO might be SOMETHING WRONG HERe } } else { auto segment = _brush->getSegmentToPaintInRect(intersection); auto point1 = this->convertToNodeSpace(segment.point1); auto point2 = this->convertToNodeSpace(segment.point2); this->drawLine(point1, point2, segment.thickness); this->markAutofillRectangles(point1, point2); } _lastBrushIntersection = intersection; } } void ToyColourableSprite::repaintTextures(bool clear) { if(clear){ _renderTextureWhite->beginWithClear(0,0,0,0); } else { _renderTextureWhite->begin(); } _drawNodeWhite->Node::visit(); _whiteSprite->visit(); // _drawNodeWhite->visit(); _renderTextureWhite->end(); if(clear){ _renderTextureRevealed->beginWithClear(0,0,0,0); } else { _renderTextureRevealed->begin(); } _drawNodeRevealed->Node::visit(); // _drawNodeRevealed->visit(); _revealedSprite->visit(); _renderTextureRevealed->end(); } void ToyColourableSprite::drawOnTextures(std::function drawNodeDrawBlock, bool clearTextures) { drawNodeDrawBlock(_drawNodeWhite); drawNodeDrawBlock(_drawNodeRevealed); if(!_deferringRepaint){ this->repaintTextures(clearTextures); } } void ToyColourableSprite::drawLine(cocos2d::Point location1, cocos2d::Point location2, float brushThickness) { this->drawOnTextures([&](cocos2d::DrawNode* drawNode){ drawNode->drawSegment(location1, location2, brushThickness, cocos2d::Color4F(1, 1, 1, 1)); }, false); } void ToyColourableSprite::clear() { for(int i = 0; i < _autofillRects.size(); ++i){ _autofillRects[i].visited = false; } this->drawOnTextures([](cocos2d::DrawNode* drawNode){ drawNode->clear(); }, true); } void ToyColourableSprite::tempClear() { this->drawOnTextures([](cocos2d::DrawNode* drawNode){ drawNode->clear(); }, true); } void ToyColourableSprite::tempRefill() { this->drawOnTextures([&](cocos2d::DrawNode* drawNode){ drawNode->clear(); drawNode->drawSolidRect(cocos2d::Point(0,0), cocos2d::Point(_revealedSprite->getBoundingBox().size.width, _revealedSprite->getBoundingBox().size.height), cocos2d::Color4F(1, 1, 1, 1)); }, true); } void ToyColourableSprite::markAutofillRectangles(cocos2d::Point point1, cocos2d::Point point2) { for(int i = 0; i < _autofillRects.size(); ++i){ if(!_autofillRects[i].visited){ if(ToyGeometryUtils::segmentIntersectsRect(_autofillRects[i].rect, point1, point2)){ _autofillRects[i].visited = true; } } } } void ToyColourableSprite::markAutofillRectanglesByPoint(cocos2d::Point point) { for(int i = 0; i < _autofillRects.size(); ++i){ if(!_autofillRects[i].visited){ if(_autofillRects[i].rect.containsPoint(point)){ _autofillRects[i].visited = true; } } } } void ToyColourableSprite::markAutofillRectanglesByRect(cocos2d::Rect rect) { for(int i = 0; i < _autofillRects.size(); ++i){ if(!_autofillRects[i].visited){ if(_autofillRects[i].rect.intersectsRect(rect)){ _autofillRects[i].visited = true; } } } } bool ToyColourableSprite::checkAutofillRectangles() { for(int i = 0; i < _autofillRects.size(); ++i) { if(!_autofillRects[i].visited){ return false; } } return true; } void ToyColourableSprite::autofill() { if(!_autofilled){ _autofilled = true; this->drawOnTextures([&](cocos2d::DrawNode* drawNode){ drawNode->clear(); drawNode->drawSolidRect(cocos2d::Point(0,0), cocos2d::Point(_revealedSprite->getBoundingBox().size.width, _revealedSprite->getBoundingBox().size.height), cocos2d::Color4F(1, 1, 1, 1)); }, false); _colouringEnabled = false; _autofillCallback(); } } void ToyColourableSprite::flashOnce(float delaySecs) { if(_autofilled){ this->tempClear(); } auto fadeInAction = cocos2d::FadeIn::create(0.5); auto delayAction = cocos2d::DelayTime::create(delaySecs); auto fadeOutAction = cocos2d::FadeOut::create(0.5); auto onCompleteAction = cocos2d::CallFunc::create([&](){ if(_autofilled){ this->tempRefill(); } }); _flashingSprite->runAction(cocos2d::Sequence::create(fadeInAction, delayAction, fadeOutAction, onCompleteAction, NULL)); } void ToyColourableSprite::flash(float time, int times) { if(_autofilled){ this->tempClear(); } auto delaySecs = time/times - 0.6; auto fadeInAction = cocos2d::FadeIn::create(0.3); auto delayAction = cocos2d::DelayTime::create(delaySecs); auto fadeOutAction = cocos2d::FadeOut::create(0.3); _flashingSprite->runAction(cocos2d::Sequence::create(cocos2d::Repeat::create(cocos2d::Sequence::create(fadeInAction, delayAction, fadeOutAction, NULL), times), cocos2d::CallFunc::create([&]() { if(_autofilled){ this->tempRefill(); } }), NULL)); } void ToyColourableSprite::stopFlashing() { _flashingSprite->stopAllActions(); _flashingSprite->setOpacity(0); if (_autofilled) { this->tempRefill(); } } // need to overridee this due to the renderTexture void ToyColourableSprite::setOpacity(uint8_t op) { _whiteSprite->setOpacity(op); _revealedSprite->setOpacity(op); //refresh this->repaintTextures(false); } void ToyColourableSprite::startAutofillAnimation() { _colouringEnabled = false; _autofilling = true; _sumDy = _brushThickness/2; this->scheduleUpdate(); } void ToyColourableSprite::startDemoAnimation(ToyDraggableBrushSprite* brush) { _colouringEnabled = false; _playingDemoAnimation = true; _currentRoutePointIndex = 0; this->startUsingBrush(brush); brush->stopAllActions(); //todo think if it shouldnt be somewhere else brush->getParent()->reorderChild(brush, 10); //todo think if it shouldnt be somewhere else this->scheduleUpdate(); } void ToyColourableSprite::update(float dt) { ToyPlainNode::update(dt); if(_autofilling){ auto dy = _autofillVelocity * dt; auto newSumDy = _sumDy + dy; auto convertedY1 = this->getBoundingBox().size.height - _sumDy; auto convertedY2 = this->getBoundingBox().size.height - newSumDy; for(float y = convertedY1; y > convertedY2; y -= _brushThickness){ if(y < _brushThickness/2){ _autofilling = false; this->unscheduleUpdate(); this->autofill(); break; } else { this->drawLine(cocos2d::Point(0, y), cocos2d::Point(this->getBoundingBox().size.width, y), _brushThickness); } } _sumDy = newSumDy; } else if(_playingDemoAnimation){ auto newRoutePoints = ceil(_brush->BRUSH_DEMO_VELOCITY * dt); auto lastRoutePoint = _currentRoutePointIndex + newRoutePoints; if(lastRoutePoint >= _brush->_demoRoute.size() - 1){ lastRoutePoint = _brush->_demoRoute.size() - 1; } _deferringRepaint = true; for(int i = _currentRoutePointIndex; i <= lastRoutePoint; ++i){ //todo paint whole bunch on texture _brush->setPosition(cocos2d::Point(_brush->_demoRoute[i].x, _brush->_demoRoute[i].y)); if(i == lastRoutePoint){ _deferringRepaint = false; } this->paintWithBrush(_brush, false); } _currentRoutePointIndex = lastRoutePoint; if(lastRoutePoint == _brush->_demoRoute.size() - 1){ this->endUsingBrush(); _playingDemoAnimation = false; this->unscheduleUpdate(); } } else if(_drawingWithFinger){ if(_previousTouchPoint != _currentTouchPoint){ drawLine(_previousTouchPoint, _currentTouchPoint, _brushThickness); this->markAutofillRectangles(_previousTouchPoint, _currentTouchPoint); _previousTouchPoint = _currentTouchPoint; } } } // Scenario Object void ToyColourableSprite::setProperty(std::string propertyName, const rapidjson::Value& newValue, ActionParseDelegate* parseDelegate) { ToyPlainNode::setProperty(propertyName, newValue, parseDelegate); if(propertyName == "colouringEnabled"){ if(newValue.IsInt()){ _colouringEnabled = newValue.GetInt(); } else if(newValue.IsBool()){ _colouringEnabled = newValue.GetBool(); } } else if(propertyName == "opacity"){ auto value = newValue.GetInt(); this->setOpacity(value); } } void ToyColourableSprite::callFunctionByName(std::string methodName, const rapidjson::Value* arguments, ActionParseDelegate* parseDelegate, std::function callback) { ToyPlainNode::callFunctionByName(methodName, arguments, parseDelegate, callback); if(methodName == "flash"){ if((*arguments).IsArray()){ auto args = (*arguments).GetArray(); this->flash(args[0].GetFloat(), args[1].GetInt()); } } else if(methodName == "startDemoAnimation"){ if((*arguments).IsArray()){ auto args = (*arguments).GetArray(); auto brush = ToyActionParser::getInstance().parseObject(args[0]["objectName"].GetString(), parseDelegate); auto brushAsBrush = dynamic_cast(brush); if(brushAsBrush){ this->startDemoAnimation(brushAsBrush); } } } else if(methodName == "startUsingBrush"){ if((*arguments).IsArray()){ auto args = (*arguments).GetArray(); auto brush = ToyActionParser::getInstance().parseObject(args[0]["objectName"].GetString(), parseDelegate); auto brushAsBrush = dynamic_cast(brush); if(brushAsBrush){ this->startUsingBrush(brushAsBrush); } } } } std::string ToyColourableSprite::getPropertyAsString(std::string propertyName) { if(propertyName == "colouringEnabled"){ if(_colouringEnabled){ return "1"; } else { return "0"; } } return "NULL"; }