#include "StarNetworkedAnimator.hpp" #include "StarJsonExtra.hpp" #include "StarIterator.hpp" #include "StarParticleDatabase.hpp" #include "StarRoot.hpp" #include "StarAssets.hpp" #include "StarLexicalCast.hpp" #include "StarDataStreamExtra.hpp" #include "StarRandom.hpp" #include "StarGameTypes.hpp" namespace Star { NetworkedAnimator::DynamicTarget::~DynamicTarget() { stopAudio(); } List NetworkedAnimator::DynamicTarget::pullNewAudios() { pendingAudios.exec([this](AudioInstancePtr const& ptr) { Vec2F audioBasePosition = ptr->position().value(); currentAudioBasePositions[ptr] = audioBasePosition; ptr->setPosition(position + audioBasePosition); }); return take(pendingAudios); } List NetworkedAnimator::DynamicTarget::pullNewParticles() { pendingParticles.exec([this](Particle& particle) { particle.position += position; return particle; }); return take(pendingParticles); } void NetworkedAnimator::DynamicTarget::stopAudio() { for (auto const& pair : currentAudioBasePositions) { if (pair.first->loops() != 0) pair.first->stop(); } } void NetworkedAnimator::DynamicTarget::updatePosition(Vec2F const& p) { clearFinishedAudio(); position = p; for (auto& audioPair : currentAudioBasePositions) audioPair.first->setPosition(audioPair.second + p); } void NetworkedAnimator::DynamicTarget::clearFinishedAudio() { for (auto& p : statePersistentSounds) { if (p.second.audio && p.second.audio->finished()) p.second.audio.reset(); } for (auto& p : stateImmediateSounds) { if (p.second.audio && p.second.audio->finished()) p.second.audio.reset(); } for (auto& p : independentSounds) eraseWhere(p.second, [](AudioInstancePtr const& audio) { return audio->finished(); }); eraseWhere(currentAudioBasePositions, [](pair const& pair) { return pair.first->finished(); }); } NetworkedAnimator::NetworkedAnimator() { m_zoom.set(1.0f); m_flipped.set(false); m_flippedRelativeCenterLine.set(0.0f); m_animationRate.set(1.0f); setupNetStates(); } NetworkedAnimator::NetworkedAnimator(Json config, String relativePath) : NetworkedAnimator() { auto& root = Root::singleton(); if (config.isNull()) return; if (config.type() == Json::Type::String) { if (relativePath.empty()) relativePath = config.toString(); config = root.assets()->json(AssetPath::relativeTo(relativePath, config.toString())); } else { if (relativePath.empty()) relativePath = "/"; } m_animatedParts = AnimatedPartSet(config.get("animatedParts", JsonObject())); m_relativePath = AssetPath::directory(relativePath); for (auto const& pair : config.get("globalTagDefaults", JsonObject()).iterateObject()) setGlobalTag(pair.first, pair.second.toString()); for (auto const& part : config.get("partTagDefaults", JsonObject()).iterateObject()) { for (auto const& tag : part.second.iterateObject()) setPartTag(part.first, tag.first, tag.second.toString()); } for (auto const& pair : config.get("transformationGroups", JsonObject()).iterateObject()) { auto& tg = m_transformationGroups[pair.first]; tg.interpolated = pair.second.getBool("interpolated", false); tg.setAffineTransform(Mat3F::identity()); } for (auto const& pair : config.get("rotationGroups", JsonObject()).iterateObject()) { String rotationGroupName = pair.first; Json rotationGroupConfig = pair.second; RotationGroup& rotationGroup = m_rotationGroups[move(rotationGroupName)]; rotationGroup.angularVelocity = rotationGroupConfig.getFloat("angularVelocity", 0.0f); rotationGroup.rotationCenter = jsonToVec2F(rotationGroupConfig.get("rotationCenter", JsonArray{0, 0})); } for (auto const& pair : config.get("particleEmitters", JsonObject()).iterateObject()) { String particleEmitterName = pair.first; Json particleEmitterConfig = pair.second; ParticleEmitter& emitter = m_particleEmitters[move(particleEmitterName)]; emitter.emissionRate.set(particleEmitterConfig.getFloat("emissionRate", 1.0f)); emitter.emissionRateVariance = particleEmitterConfig.getFloat("emissionRateVariance", 0.0f); emitter.offsetRegion.set(particleEmitterConfig.opt("offsetRegion").apply(jsonToRectF).value(RectF::null())); emitter.anchorPart = particleEmitterConfig.optString("anchorPart"); emitter.transformationGroups = jsonToStringList(particleEmitterConfig.get("transformationGroups", JsonArray())); emitter.rotationGroup = particleEmitterConfig.optString("rotationGroup"); emitter.rotationCenter = particleEmitterConfig.opt("rotationCenter").apply(jsonToVec2F); for (auto const& config : particleEmitterConfig.get("particles").iterateArray()) { auto creator = root.particleDatabase()->particleCreator(config.get("particle"), relativePath); unsigned count = config.getUInt("count", 1); Vec2F offset = jsonToVec2F(config.get("offset", JsonArray{0, 0})); bool flip = config.getBool("flip", false); emitter.particleList.append({creator, count, offset, flip}); } // default to one cycle through the particle list in a burst emitter.burstCount.set(particleEmitterConfig.getUInt("burstCount", 1)); // default to one of each to preserve current behaviour. emitter.randomSelectCount.set(particleEmitterConfig.getUInt("randomSelectCount", emitter.particleList.size())); emitter.active.set(particleEmitterConfig.getBool("active", false)); } for (auto const& pair : config.get("lights", JsonObject()).iterateObject()) { String lightName = pair.first; Json lightConfig = pair.second; Light& light = m_lights[move(lightName)]; light.active.set(lightConfig.getBool("active", true)); auto lightPosition = lightConfig.opt("position").apply(jsonToVec2F).value(); light.xPosition.set(lightPosition[0]); light.yPosition.set(lightPosition[1]); light.color.set(lightConfig.opt("color").apply(jsonToColor).value(Color::White)); light.anchorPart = lightConfig.optString("anchorPart"); light.transformationGroups = jsonToStringList(lightConfig.get("transformationGroups", JsonArray())); light.rotationGroup = lightConfig.optString("rotationGroup"); light.rotationCenter = lightConfig.opt("rotationCenter").apply(jsonToVec2F); if (lightConfig.contains("flickerPeriod")) { light.flicker = PeriodicFunction( lightConfig.getFloat("flickerPeriod"), lightConfig.getFloat("flickerMinIntensity", 0.0), lightConfig.getFloat("flickerMaxIntensity", 0.0), lightConfig.getFloat("flickerPeriodVariance", 0.0), lightConfig.getFloat("flickerIntensityVariance", 0.0) ); } light.pointAngle.set(lightConfig.getFloat("pointAngle", 0.0f) * Constants::deg2rad); light.pointLight = lightConfig.getBool("pointLight", false); light.pointBeam = lightConfig.getFloat("pointBeam", 0.0f); light.beamAmbience = lightConfig.getFloat("beamAmbience", 0.0f); } for (auto const& pair : config.get("sounds", JsonObject()).iterateObject()) { String soundName = pair.first; Json soundConfig = pair.second; Sound& sound = m_sounds[move(soundName)]; if (soundConfig.isType(Json::Type::Array)) { sound.rangeMultiplier = 1.0f; sound.soundPool.set(jsonToStringList(soundConfig).transformed(bind(&AssetPath::relativeTo, m_relativePath, _1))); sound.volumeTarget.set(1.0f); sound.volumeRampTime.set(0.0f); sound.pitchMultiplierTarget.set(1.0f); sound.pitchMultiplierRampTime.set(0.0f); } else { sound.rangeMultiplier = soundConfig.getFloat("rangeMultiplier", 1.0f); auto soundPosition = soundConfig.opt("position").apply(jsonToVec2F).value(); sound.xPosition.set(soundPosition[0]); sound.yPosition.set(soundPosition[1]); sound.volumeTarget.set(soundConfig.getFloat("volume", 1.0f)); sound.volumeRampTime.set(soundConfig.getFloat("volumeRampTime", 0.0f)); sound.pitchMultiplierTarget.set(soundConfig.getFloat("pitchMultiplier", 1.0f)); sound.pitchMultiplierRampTime.set(soundConfig.getFloat("pitchMultiplierRampTime", 0.0f)); sound.soundPool.set(jsonToStringList(soundConfig.get("pool", JsonArray())).transformed(bind(&AssetPath::relativeTo, m_relativePath, _1))); } } for (auto const& pair : config.get("effects", JsonObject()).iterateObject()) { String effectName = pair.first; Json effectConfig = pair.second; Effect& effect = m_effects[effectName]; effect.type = effectConfig.getString("type"); effect.time = effectConfig.getFloat("time", 0.0f); effect.directives = effectConfig.getString("directives"); } // Sort all the states that contain NetStates handles predictably by key. m_transformationGroups.sortByKey(); m_rotationGroups.sortByKey(); m_particleEmitters.sortByKey(); m_lights.sortByKey(); m_sounds.sortByKey(); m_effects.sortByKey(); // Make sure that every state type has an entry in the state info map, and // order it predictably by key. for (auto const& stateType : m_animatedParts.stateTypes()) m_stateInfo[stateType]; m_stateInfo.sortByKey(); setupNetStates(); } NetworkedAnimator::NetworkedAnimator(NetworkedAnimator&& animator) { operator=(move(animator)); } NetworkedAnimator::NetworkedAnimator(NetworkedAnimator const& animator) { operator=(animator); } NetworkedAnimator& NetworkedAnimator::operator=(NetworkedAnimator&& animator) { m_relativePath = move(animator.m_relativePath); m_animatedParts = move(animator.m_animatedParts); m_stateInfo = move(animator.m_stateInfo); m_transformationGroups = move(animator.m_transformationGroups); m_rotationGroups = move(animator.m_rotationGroups); m_particleEmitters = move(animator.m_particleEmitters); m_lights = move(animator.m_lights); m_sounds = move(animator.m_sounds); m_effects = move(animator.m_effects); m_processingDirectives = move(animator.m_processingDirectives); m_zoom = move(animator.m_zoom); m_flipped = move(animator.m_flipped); m_flippedRelativeCenterLine = move(animator.m_flippedRelativeCenterLine); m_animationRate = move(animator.m_animationRate); m_globalTags = move(animator.m_globalTags); m_partTags = move(animator.m_partTags); setupNetStates(); return *this; } NetworkedAnimator& NetworkedAnimator::operator=(NetworkedAnimator const& animator) { m_relativePath = animator.m_relativePath; m_animatedParts = animator.m_animatedParts; m_stateInfo = animator.m_stateInfo; m_transformationGroups = animator.m_transformationGroups; m_rotationGroups = animator.m_rotationGroups; m_particleEmitters = animator.m_particleEmitters; m_lights = animator.m_lights; m_sounds = animator.m_sounds; m_effects = animator.m_effects; m_processingDirectives = animator.m_processingDirectives; m_zoom = animator.m_zoom; m_flipped = animator.m_flipped; m_flippedRelativeCenterLine = animator.m_flippedRelativeCenterLine; m_animationRate = animator.m_animationRate; m_globalTags = animator.m_globalTags; m_partTags = animator.m_partTags; setupNetStates(); return *this; } StringList NetworkedAnimator::stateTypes() const { return m_animatedParts.stateTypes(); } StringList NetworkedAnimator::states(String const& stateType) const { return m_animatedParts.states(stateType); } bool NetworkedAnimator::setState(String const& stateType, String const& state, bool startNew) { if (m_animatedParts.setActiveState(stateType, state, startNew)) { m_stateInfo[stateType].startedEvent.trigger(); return true; } else { return false; } } String NetworkedAnimator::state(String const& stateType) const { return m_animatedParts.activeState(stateType).stateName; } StringList NetworkedAnimator::parts() const { return m_animatedParts.parts(); } Json NetworkedAnimator::stateProperty(String const& stateType, String const& propertyName) const { return m_animatedParts.activeState(stateType).properties.value(propertyName); } Json NetworkedAnimator::partProperty(String const& partName, String const& propertyName) const { return m_animatedParts.activePart(partName).properties.value(propertyName); } Mat3F NetworkedAnimator::globalTransformation() const { Mat3F transformation = Mat3F::scaling(m_zoom.get()); if (m_flipped.get()) transformation = Mat3F::scaling(Vec2F(-1, 1), Vec2F(m_flippedRelativeCenterLine.get(), 0)) * transformation; return transformation; } Mat3F NetworkedAnimator::groupTransformation(StringList const& transformationGroups) const { auto mat = Mat3F::identity(); for (auto const& tg : transformationGroups) mat = m_transformationGroups.get(tg).affineTransform() * mat; return mat; } Mat3F NetworkedAnimator::partTransformation(String const& partName) const { auto const& part = m_animatedParts.activePart(partName); Mat3F transformation = Mat3F::identity(); if (auto offset = part.properties.value("offset").opt().apply(jsonToVec2F)) transformation = Mat3F::translation(*offset) * transformation; auto transformationGroups = jsonToStringList(part.properties.value("transformationGroups", JsonArray())); transformation = groupTransformation(transformationGroups) * transformation; if (auto rotationGroupName = part.properties.value("rotationGroup").optString()) { auto const& rotationGroup = m_rotationGroups.get(*rotationGroupName); Vec2F rotationCenter = part.properties.value("rotationCenter").opt().apply(jsonToVec2F).value(rotationGroup.rotationCenter); transformation = Mat3F::rotation(rotationGroup.currentAngle, rotationCenter) * transformation; } if (auto anchorPart = part.properties.ptr("anchorPart")) transformation = partTransformation(anchorPart->toString()) * transformation; return transformation; } Mat3F NetworkedAnimator::finalPartTransformation(String const& partName) const { return globalTransformation() * partTransformation(partName); } Maybe NetworkedAnimator::partPoint(String const& partName, String const& propertyName) const { auto const& part = m_animatedParts.activePart(partName); auto property = part.properties.value(propertyName); if (!property) return {}; return finalPartTransformation(partName).transformVec2(jsonToVec2F(property)); } Maybe NetworkedAnimator::partPoly(String const& partName, String const& propertyName) const { auto const& part = m_animatedParts.activePart(partName); auto property = part.properties.value(propertyName, {}); if (!property) return {}; PolyF poly = jsonToPolyF(property); poly.transform(finalPartTransformation(partName)); return poly; } void NetworkedAnimator::setGlobalTag(String tagName, String tagValue) { m_globalTags.set(move(tagName), move(tagValue)); } void NetworkedAnimator::setPartTag(String const& partType, String tagName, String tagValue) { m_partTags[partType].set(move(tagName), move(tagValue)); } void NetworkedAnimator::setProcessingDirectives(Directives const& directives) { m_processingDirectives.set(directives); } void NetworkedAnimator::setZoom(float zoom) { m_zoom.set(zoom); } bool NetworkedAnimator::flipped() const { return m_flipped.get(); } float NetworkedAnimator::flippedRelativeCenterLine() const { return m_flippedRelativeCenterLine.get(); } void NetworkedAnimator::setFlipped(bool flipped, float relativeCenterLine) { m_flipped.set(flipped); m_flippedRelativeCenterLine.set(relativeCenterLine); } void NetworkedAnimator::setAnimationRate(float rate) { m_animationRate.set(rate); } bool NetworkedAnimator::hasRotationGroup(String const& rotationGroup) const { return m_rotationGroups.contains(rotationGroup); } void NetworkedAnimator::rotateGroup(String const& rotationGroup, float targetAngle, bool immediate) { auto& group = m_rotationGroups.get(rotationGroup); group.targetAngle.set(targetAngle); if (immediate) { group.currentAngle = targetAngle; group.netImmediateEvent.trigger(); } } float NetworkedAnimator::currentRotationAngle(String const& rotationGroup) const { return m_rotationGroups.get(rotationGroup).currentAngle; } bool NetworkedAnimator::hasTransformationGroup(String const& transformationGroup) const { return m_transformationGroups.contains(transformationGroup); } void NetworkedAnimator::translateTransformationGroup(String const& transformationGroup, Vec2F const& translation) { auto& group = m_transformationGroups.get(transformationGroup); group.setAffineTransform(Mat3F::translation(translation) * group.affineTransform()); } void NetworkedAnimator::rotateTransformationGroup( String const& transformationGroup, float rotation, Vec2F const& rotationCenter) { auto& group = m_transformationGroups.get(transformationGroup); group.setAffineTransform(Mat3F::rotation(rotation, rotationCenter) * group.affineTransform()); } void NetworkedAnimator::scaleTransformationGroup( String const& transformationGroup, float scale, Vec2F const& scaleCenter) { auto& group = m_transformationGroups.get(transformationGroup); group.setAffineTransform(Mat3F::scaling(scale, scaleCenter) * group.affineTransform()); } void NetworkedAnimator::scaleTransformationGroup( String const& transformationGroup, Vec2F const& scale, Vec2F const& scaleCenter) { auto& group = m_transformationGroups.get(transformationGroup); group.setAffineTransform(Mat3F::scaling(scale, scaleCenter) * group.affineTransform()); } void NetworkedAnimator::transformTransformationGroup( String const& transformationGroup, float a, float b, float c, float d, float tx, float ty) { auto& group = m_transformationGroups.get(transformationGroup); Mat3F transform = Mat3F(a, b, tx, c, d, ty, 0, 0, 1); group.setAffineTransform(transform * group.affineTransform()); } void NetworkedAnimator::resetTransformationGroup(String const& transformationGroup) { m_transformationGroups.get(transformationGroup).setAffineTransform(Mat3F::identity()); } bool NetworkedAnimator::hasParticleEmitter(String const& emitterName) const { return m_particleEmitters.contains(emitterName); } void NetworkedAnimator::setParticleEmitterActive(String const& emitterName, bool active) { m_particleEmitters.get(emitterName).active.set(active); } void NetworkedAnimator::setParticleEmitterEmissionRate(String const& emitterName, float emissionRate) { m_particleEmitters.get(emitterName).emissionRate.set(emissionRate); } void NetworkedAnimator::setParticleEmitterOffsetRegion(String const& emitterName, RectF const& offsetRegion) { m_particleEmitters.get(emitterName).offsetRegion.set(offsetRegion); } void NetworkedAnimator::setParticleEmitterBurstCount(String const& emitterName, unsigned burstCount) { m_particleEmitters.get(emitterName).burstCount.set(burstCount); } void NetworkedAnimator::burstParticleEmitter(String const& emitterName) { m_particleEmitters.get(emitterName).burstEvent.trigger(); } bool NetworkedAnimator::hasLight(String const& lightName) const { return m_lights.contains(lightName); } void NetworkedAnimator::setLightActive(String const& lightName, bool active) { m_lights.get(lightName).active.set(active); } void NetworkedAnimator::setLightPosition(String const& lightName, Vec2F position) { auto& light = m_lights.get(lightName); light.xPosition.set(position[0]); light.yPosition.set(position[1]); } void NetworkedAnimator::setLightColor(String const& lightName, Color color) { m_lights.get(lightName).color.set(color); } void NetworkedAnimator::setLightPointAngle(String const& lightName, float angle) { m_lights.get(lightName).pointAngle.set(angle * Constants::deg2rad); } bool NetworkedAnimator::hasSound(String const& soundName) const { return m_sounds.contains(soundName); } void NetworkedAnimator::setSoundPool(String const& soundName, StringList soundPool) { m_sounds.get(soundName).soundPool.set(move(soundPool)); } void NetworkedAnimator::setSoundPosition(String const& soundName, Vec2F const& position) { auto& sound = m_sounds.get(soundName); sound.xPosition.set(position[0]); sound.yPosition.set(position[1]); } void NetworkedAnimator::setSoundVolume(String const& soundName, float volume, float rampTime) { auto& sound = m_sounds.get(soundName); sound.volumeTarget.set(volume); sound.volumeRampTime.set(rampTime); } void NetworkedAnimator::setSoundPitchMultiplier(String const& soundName, float pitchMultiplier, float rampTime) { auto& sound = m_sounds.get(soundName); sound.pitchMultiplierTarget.set(pitchMultiplier); sound.pitchMultiplierRampTime.set(rampTime); } void NetworkedAnimator::playSound(String const& soundName, int loops) { auto& sound = m_sounds.get(soundName); sound.loops.set(loops); sound.signals.send(SoundSignal::Play); } void NetworkedAnimator::stopAllSounds(String const& soundName, float rampTime) { auto& sound = m_sounds.get(soundName); sound.volumeRampTime.set(rampTime); sound.signals.send(SoundSignal::StopAll); } void NetworkedAnimator::setEffectEnabled(String const& effect, bool enabled) { m_effects.get(effect).enabled.set(enabled); } List NetworkedAnimator::drawables(Vec2F const& position) const { List drawables; for (auto& p : drawablesWithZLevel(position)) drawables.append(move(p.first)); return drawables; } List> NetworkedAnimator::drawablesWithZLevel(Vec2F const& position) const { List baseProcessingDirectives = { m_processingDirectives.get() }; for (auto& pair : m_effects) { auto const& effectState = pair.second; if (effectState.enabled.get()) { auto const& effect = m_effects.get(pair.first); if (effect.type == "flash") { if (effectState.timer > effect.time / 2) { baseProcessingDirectives.append(effect.directives); } } else if (effect.type == "directive") { baseProcessingDirectives.append(effect.directives); } else { throw NetworkedAnimatorException(strf("No such NetworkedAnimator effect type '%s'", effect.type)); } } } List> drawables; m_animatedParts.forEachActivePart([&](String const& partName, AnimatedPartSet::ActivePartInformation const& activePart) { // Make sure we don't copy the original image String fallback = ""; Json jImage = activePart.properties.value("image", {}); String const& image = jImage.isType(Json::Type::String) ? *jImage.stringPtr() : fallback; bool centered = activePart.properties.value("centered").optBool().value(true); bool fullbright = activePart.properties.value("fullbright").optBool().value(false); auto maybeZLevel = activePart.properties.value("zLevel").optFloat(); if (m_flipped.get()) maybeZLevel = activePart.properties.value("flippedZLevel").optFloat().orMaybe(maybeZLevel); float zLevel = maybeZLevel.value(0.0f); size_t originalDirectivesSize = baseProcessingDirectives.size(); if (auto directives = activePart.properties.value("processingDirectives").optString()) { baseProcessingDirectives.append(*directives); } Maybe frame; String frameStr; String frameIndexStr; if (activePart.activeState) { unsigned stateFrame = activePart.activeState->frame; frame = stateFrame; frameStr = static_cast(toString(stateFrame + 1)); frameIndexStr = static_cast(toString(stateFrame)); if (auto directives = activePart.activeState->properties.value("processingDirectives").optString()) { baseProcessingDirectives.append(*directives); } } auto const& partTags = m_partTags.get(partName); Maybe processedImage = image.maybeLookupTagsView([&](StringView tag) -> StringView { if (tag == "frame") { if (frame) return frameStr; } else if (tag == "frameIndex") { if (frame) return frameIndexStr; } else if (auto p = partTags.ptr(tag)) { return StringView(*p); } else if (auto p = m_globalTags.ptr(tag)) { return StringView(*p); } return StringView("default"); }); String const& usedImage = processedImage ? processedImage.get() : image; if (!usedImage.empty() && usedImage[0] != ':' && usedImage[0] != '?') { String relativeImage; if (usedImage[0] != '/') relativeImage = AssetPath::relativeTo(m_relativePath, usedImage); auto drawable = Drawable::makeImage(!relativeImage.empty() ? relativeImage : usedImage, 1.0f / TilePixels, centered, Vec2F()); auto& imagePart = drawable.imagePart(); for (Directives const& directives : baseProcessingDirectives) imagePart.addDirectives(directives); drawable.transform(partTransformation(partName)); drawable.transform(globalTransformation()); drawable.fullbright = fullbright; drawable.translate(position); drawables.append({move(drawable), zLevel}); } baseProcessingDirectives.resize(originalDirectivesSize); }); sort(drawables, [](auto const& a, auto const& b) { return a.second < b.second; }); return drawables; } List NetworkedAnimator::lightSources(Vec2F const& translate) const { List lightSources; for (auto const& pair : m_lights) { if (!pair.second.active.get()) continue; Vec2F position = {pair.second.xPosition.get(), pair.second.yPosition.get()}; float pointAngle = constrainAngle(pair.second.pointAngle.get()); Mat3F transformation = Mat3F::identity(); if (pair.second.anchorPart) transformation = partTransformation(*pair.second.anchorPart); transformation = groupTransformation(pair.second.transformationGroups) * transformation; position = transformation.transformVec2(position); pointAngle = transformation.transformAngle(pointAngle); if (pair.second.rotationGroup) { auto const& rg = m_rotationGroups.get(*pair.second.rotationGroup); position = (position - pair.second.rotationCenter.value(rg.rotationCenter)).rotate(rg.currentAngle) + pair.second.rotationCenter.value(rg.rotationCenter); pointAngle += rg.currentAngle; } position = globalTransformation().transformVec2(position); if (m_flipped.get()) { if (pointAngle > 0) pointAngle = Constants::pi / 2 + constrainAngle(Constants::pi / 2 - pointAngle); else pointAngle = -Constants::pi / 2 - constrainAngle(pointAngle + Constants::pi / 2); } Color color = pair.second.color.get(); if (pair.second.flicker) color.setValue(clamp(color.value() * pair.second.flicker->value(SinWeightOperator()), 0.0f, 1.0f)); lightSources.append(LightSource{ position + translate, color.toRgb(), pair.second.pointLight, pair.second.pointBeam, pointAngle, pair.second.beamAmbience }); } return lightSources; } void NetworkedAnimator::update(float dt, DynamicTarget* dynamicTarget) { dt *= m_animationRate.get(); m_animatedParts.update(dt); m_animatedParts.forEachActiveState([&](String const& stateTypeName, AnimatedPartSet::ActiveStateInformation const& activeState) { if (dynamicTarget) { dynamicTarget->clearFinishedAudio(); String persistentSoundFile = activeState.properties.value("persistentSound", "").toString(); if (!persistentSoundFile.empty()) persistentSoundFile = AssetPath::relativeTo(m_relativePath, persistentSoundFile); auto& activePersistentSound = dynamicTarget->statePersistentSounds[stateTypeName]; if (persistentSoundFile != activePersistentSound.file || !activePersistentSound.audio) { activePersistentSound.file = persistentSoundFile; if (activePersistentSound.audio) activePersistentSound.audio->stop(activePersistentSound.stopRampTime); if (!persistentSoundFile.empty()) { activePersistentSound.audio = make_shared(*Root::singleton().assets()->audio(persistentSoundFile)); activePersistentSound.audio->setRangeMultiplier(activeState.properties.value("persistentSoundRangeMultiplier", 1.0f).toFloat()); activePersistentSound.audio->setLoops(-1); activePersistentSound.audio->setPosition(globalTransformation().transformVec2(Vec2F())); activePersistentSound.stopRampTime = activeState.properties.value("persistentSoundStopTime", 0.0f).toFloat(); dynamicTarget->pendingAudios.append(activePersistentSound.audio); } else { dynamicTarget->statePersistentSounds.remove(stateTypeName); } } String immediateSoundFile = activeState.properties.value("immediateSound", "").toString(); if (!immediateSoundFile.empty()) immediateSoundFile = AssetPath::relativeTo(m_relativePath, immediateSoundFile); auto& activeImmediateSound = dynamicTarget->stateImmediateSounds[stateTypeName]; if (immediateSoundFile != activeImmediateSound.file) { activeImmediateSound.file = immediateSoundFile; if (!immediateSoundFile.empty()) { activeImmediateSound.audio = make_shared(*Root::singleton().assets()->audio(immediateSoundFile)); activeImmediateSound.audio->setRangeMultiplier(activeState.properties.value("immediateSoundRangeMultiplier", 1.0f).toFloat()); activeImmediateSound.audio->setPosition(globalTransformation().transformVec2(Vec2F())); dynamicTarget->pendingAudios.append(activeImmediateSound.audio); } } } if (auto lightsOn = activeState.properties.ptr("lightsOn")) { for (auto const& name : lightsOn->iterateArray()) m_lights.get(name.toString()).active.set(true); } if (auto lightsOff = activeState.properties.ptr("lightsOff")) { for (auto const& name : lightsOff->iterateArray()) m_lights.get(name.toString()).active.set(false); } if (auto particleEmittersOn = activeState.properties.ptr("particleEmittersOn")) { for (auto const& name : particleEmittersOn->iterateArray()) m_particleEmitters.get(name.toString()).active.set(true); } if (auto particleEmittersOff = activeState.properties.ptr("particleEmittersOff")) { for (auto const& name : particleEmittersOff->iterateArray()) m_particleEmitters.get(name.toString()).active.set(false); } }); for (auto& pair : m_rotationGroups) { auto& rotationGroup = pair.second; if (rotationGroup.angularVelocity == 0.0f) rotationGroup.currentAngle = rotationGroup.targetAngle.get(); else rotationGroup.currentAngle = approachAngle(rotationGroup.targetAngle.get(), rotationGroup.currentAngle, rotationGroup.angularVelocity * dt); } if (dynamicTarget) { auto addParticles = [this, dynamicTarget](ParticleEmitter::ParticleConfig const& config, RectF const& offsetRegion, Mat3F const& transformation) { for (unsigned i = 0; i < config.count; ++i) { Particle particle = config.creator(); particle.position += config.offset; if (!offsetRegion.isNull()) { particle.position[0] += Random::randf() * offsetRegion.width() + offsetRegion.xMin(); particle.position[1] += Random::randf() * offsetRegion.height() + offsetRegion.yMin(); } float speed = particle.velocity.magnitude(); particle.velocity = Vec2F::withAngle(transformation.transformAngle(particle.velocity.angle())) * speed; particle.position = transformation.transformVec2(particle.position); particle.rotation = transformation.transformAngle(particle.rotation); particle.size *= m_zoom.get(); if (config.flip) particle.flip = !particle.flip; if (transformation.determinant() < 0) { particle.flip = !particle.flip; particle.rotation += Constants::pi; } dynamicTarget->pendingParticles.append(move(particle)); } }; for (auto& pair : m_particleEmitters) { Mat3F transformation = Mat3F::identity(); if (pair.second.anchorPart) transformation = partTransformation(*pair.second.anchorPart); transformation = groupTransformation(pair.second.transformationGroups) * transformation; if (pair.second.rotationGroup) { auto const& rg = m_rotationGroups.get(*pair.second.rotationGroup); Vec2F rotationCenter = pair.second.rotationCenter.value(rg.rotationCenter); transformation = Mat3F::rotation(rg.currentAngle, rotationCenter) * transformation; } transformation = globalTransformation() * transformation; // assume we emit no particles unsigned numEmissionCycles = 0; if (pair.second.active.get()) { pair.second.timer = min(pair.second.timer, 1.0f / (pair.second.emissionRate.get() + pair.second.emissionRateVariance)); if (pair.second.timer <= 0.0f) { // timer causes us to emit one set ++numEmissionCycles; pair.second.timer = 1.0f / (pair.second.emissionRate.get() + Random::randf(-pair.second.emissionRateVariance, pair.second.emissionRateVariance)); } else { pair.second.timer -= dt; } } auto bursts = pair.second.burstEvent.pullOccurrences(); for (uint64_t i = 0; i < bursts; ++i) numEmissionCycles += pair.second.burstCount.get(); if (numEmissionCycles > 0) { RectF rect = pair.second.offsetRegion.get(); unsigned numToSelect = pair.second.randomSelectCount.get(); for (unsigned i = 0; i < numEmissionCycles; ++i) { if (numToSelect >= pair.second.particleList.size()) { for (auto const& particleConfig : pair.second.particleList) addParticles(particleConfig, rect, transformation); } else { List shuffledList = pair.second.particleList; Random::shuffle(shuffledList); for (unsigned i = 0; i < numToSelect; ++i) addParticles(shuffledList.at(i), rect, transformation); } } } } for (auto& pair : m_sounds) { auto const& soundName = pair.first; auto& soundEntry = pair.second; for (auto signal : soundEntry.signals.receive()) { if (signal == SoundSignal::StopAll) { for (auto& sound : take(dynamicTarget->independentSounds[soundName])) sound->stop(soundEntry.volumeRampTime.get()); } else if (signal == SoundSignal::Play) { String soundFile = Random::randValueFrom(soundEntry.soundPool.get()); if (!soundFile.empty()) { auto sound = make_shared(*Root::singleton().assets()->audio(soundFile)); sound->setRangeMultiplier(soundEntry.rangeMultiplier); sound->setLoops(soundEntry.loops.get()); sound->setPosition(globalTransformation().transformVec2(Vec2F(soundEntry.xPosition.get(), soundEntry.yPosition.get()))); sound->setVolume(soundEntry.volumeTarget.get(), soundEntry.volumeRampTime.get()); sound->setPitchMultiplier(soundEntry.pitchMultiplierTarget.get(), soundEntry.pitchMultiplierRampTime.get()); dynamicTarget->independentSounds[soundName].append(sound); dynamicTarget->pendingAudios.append(move(sound)); } } } // Update all still active independent sounds position, volume, and speed for (auto const& activeIndependentSound : dynamicTarget->independentSounds.value(soundName)) { if (auto basePosition = dynamicTarget->currentAudioBasePositions.ptr(activeIndependentSound)) *basePosition = globalTransformation().transformVec2(Vec2F(soundEntry.xPosition.get(), soundEntry.yPosition.get())); activeIndependentSound->setVolume(soundEntry.volumeTarget.get(), soundEntry.volumeRampTime.get()); activeIndependentSound->setPitchMultiplier(soundEntry.pitchMultiplierTarget.get(), soundEntry.pitchMultiplierRampTime.get()); } } } for (auto& pair : m_lights) { if (pair.second.flicker) pair.second.flicker->update(dt); } for (auto& pair : m_effects) { if (pair.second.enabled.get()) { auto& effect = pair.second; if (effect.timer <= 0.0f) effect.timer = effect.time; else effect.timer -= dt; } } } void NetworkedAnimator::finishAnimations() { m_animatedParts.finishAnimations(); } Mat3F NetworkedAnimator::TransformationGroup::affineTransform() const { return Mat3F( xScale.get() * cos(xShear.get()), xScale.get() * sin(xShear.get()), xTranslation.get(), yScale.get() * sin(yShear.get()), yScale.get() * cos(yShear.get()), yTranslation.get(), 0, 0, 1 ); } void NetworkedAnimator::TransformationGroup::setAffineTransform(Mat3F const& matrix) { xTranslation.set(matrix[0][2]); yTranslation.set(matrix[1][2]); xScale.set(sqrt(square(matrix[0][0]) + square(matrix[0][1]))); yScale.set(sqrt(square(matrix[1][0]) + square(matrix[1][1]))); xShear.set(atan2(matrix[0][1], matrix[0][0])); yShear.set(atan2(matrix[1][0], matrix[1][1])); } void NetworkedAnimator::setupNetStates() { clearNetElements(); addNetElement(&m_processingDirectives); addNetElement(&m_zoom); addNetElement(&m_flipped); addNetElement(&m_flippedRelativeCenterLine); addNetElement(&m_animationRate); m_animationRate.setInterpolator(lerp); addNetElement(&m_globalTags); for (auto const& part : sorted(m_animatedParts.parts())) addNetElement(&m_partTags[part]); for (auto& pair : m_stateInfo) { addNetElement(&pair.second.stateIndex); addNetElement(&pair.second.startedEvent); } for (auto& pair : m_transformationGroups) { addNetElement(&pair.second.xTranslation); addNetElement(&pair.second.yTranslation); addNetElement(&pair.second.xScale); addNetElement(&pair.second.yScale); addNetElement(&pair.second.xShear); addNetElement(&pair.second.yShear); if (pair.second.interpolated) { pair.second.xTranslation.setInterpolator(lerp); pair.second.yTranslation.setInterpolator(lerp); pair.second.xScale.setInterpolator(lerp); pair.second.yScale.setInterpolator(lerp); pair.second.xShear.setInterpolator(angleLerp); pair.second.yShear.setInterpolator(angleLerp); } } for (auto& pair : m_rotationGroups) { addNetElement(&pair.second.targetAngle); addNetElement(&pair.second.netImmediateEvent); } for (auto& pair : m_particleEmitters) { addNetElement(&pair.second.emissionRate); addNetElement(&pair.second.burstCount); addNetElement(&pair.second.randomSelectCount); addNetElement(&pair.second.offsetRegion); addNetElement(&pair.second.active); addNetElement(&pair.second.burstEvent); pair.second.burstEvent.setIgnoreOccurrencesOnNetLoad(true); } for (auto& pair : m_lights) { addNetElement(&pair.second.active); addNetElement(&pair.second.xPosition); addNetElement(&pair.second.yPosition); addNetElement(&pair.second.color); addNetElement(&pair.second.pointAngle); pair.second.xPosition.setFixedPointBase(0.0125f); pair.second.yPosition.setFixedPointBase(0.0125f); pair.second.pointAngle.setFixedPointBase(0.01f); pair.second.xPosition.setInterpolator(lerp); pair.second.yPosition.setInterpolator(lerp); pair.second.pointAngle.setInterpolator(angleLerp); } for (auto& pair : m_sounds) { addNetElement(&pair.second.soundPool); addNetElement(&pair.second.xPosition); addNetElement(&pair.second.yPosition); addNetElement(&pair.second.volumeTarget); addNetElement(&pair.second.volumeRampTime); addNetElement(&pair.second.pitchMultiplierTarget); addNetElement(&pair.second.pitchMultiplierRampTime); addNetElement(&pair.second.loops); addNetElement(&pair.second.signals); pair.second.xPosition.setFixedPointBase(0.0125f); pair.second.yPosition.setFixedPointBase(0.0125f); pair.second.xPosition.setInterpolator(lerp); pair.second.yPosition.setInterpolator(lerp); } for (auto& pair : m_effects) addNetElement(&pair.second.enabled); } void NetworkedAnimator::netElementsNeedLoad(bool initial) { for (auto& pair : m_stateInfo) { if (pair.second.startedEvent.pullOccurred() || initial) m_animatedParts.setActiveStateIndex(pair.first, pair.second.stateIndex.get(), true); } for (auto& pair : m_rotationGroups) { if (pair.second.netImmediateEvent.pullOccurred() || initial) pair.second.currentAngle = pair.second.targetAngle.get(); } } void NetworkedAnimator::netElementsNeedStore() { for (auto& pair : m_stateInfo) pair.second.stateIndex.set(m_animatedParts.activeStateIndex(pair.first)); } }