Beispiel #1
0
void testPropertyFlags(uint32_t value) {
    EntityPropertyFlags original;
    original.clear();
    auto enumSize = sizeof(EntityPropertyList);
    for (size_t i = 0; i < enumSize * 8; ++i) {
        original.setHasProperty((EntityPropertyList)i);
    }
    QByteArray encoded = original.encode();
    #ifndef QT_NO_DEBUG
    int originalSize = encoded.size();
    #endif
    for (size_t i = 0; i < enumSize; ++i) {
        encoded.append(qrand());
    }

    EntityPropertyFlags decodeOld, decodeNew;
    {
        decodeOld.decode(encoded);
        Q_ASSERT(decodeOld == original);
    }

    {
        #ifndef QT_NO_DEBUG
        int decodeSize = decodeNew.decode((const uint8_t*)encoded.data(), encoded.size());
        #endif
        Q_ASSERT(originalSize == decodeSize);
        Q_ASSERT(decodeNew == original);
    }
}
void EntityItemProperties::debugDump() const {
    qDebug() << "EntityItemProperties...";
    qDebug() << "    _type=" << EntityTypes::getEntityTypeName(_type);
    qDebug() << "   _id=" << _id;
    qDebug() << "   _idSet=" << _idSet;
    qDebug() << "   _position=" << _position.x << "," << _position.y << "," << _position.z;
    qDebug() << "   _dimensions=" << getDimensions();
    qDebug() << "   _modelURL=" << _modelURL;
    qDebug() << "   changed properties...";
    EntityPropertyFlags props = getChangedProperties();
    props.debugDumpBits();
}
Beispiel #3
0
int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
                                                ReadBitstreamToTreeParams& args,
                                                EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {

    int bytesRead = 0;
    const unsigned char* dataAt = data;

    READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor);
    READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL);
    if (args.bitstreamVersion < VERSION_ENTITIES_HAS_COLLISION_MODEL) {
        setCompoundShapeURL("");
    } else if (args.bitstreamVersion == VERSION_ENTITIES_HAS_COLLISION_MODEL) {
        READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
    } else {
        READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
    }
    READ_ENTITY_PROPERTY(PROP_ANIMATION_URL, QString, setAnimationURL);

    // Because we're using AnimationLoop which will reset the frame index if you change it's running state
    // we want to read these values in the order they appear in the buffer, but call our setters in an
    // order that allows AnimationLoop to preserve the correct frame rate.
    float animationFPS = getAnimationFPS();
    float animationFrameIndex = getAnimationFrameIndex();
    bool animationIsPlaying = getAnimationIsPlaying();
    READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setAnimationFPS);
    READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex);
    READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, setAnimationIsPlaying);

    if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) {
        if (animationIsPlaying != getAnimationIsPlaying()) {
            setAnimationIsPlaying(animationIsPlaying);
        }
    }
    if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) {
        setAnimationFPS(animationFPS);
    }
    if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) {
        setAnimationFrameIndex(animationFrameIndex);
    }

    READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures);
    READ_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, QString, setAnimationSettings);
    READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);

    return bytesRead;
}
int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
                                                               ReadBitstreamToTreeParams& args,
                                                               EntityPropertyFlags& propertyFlags, bool overwriteLocalData) {

    int bytesRead = 0;
    const unsigned char* dataAt = data;

    READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor);

    // Because we're using AnimationLoop which will reset the frame index if you change it's running state
    // we want to read these values in the order they appear in the buffer, but call our setters in an
    // order that allows AnimationLoop to preserve the correct frame rate.
    float animationFPS = getAnimationFPS();
    float animationFrameIndex = getAnimationFrameIndex();
    bool animationIsPlaying = getAnimationIsPlaying();
    READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setAnimationFPS);
    READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex);
    READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, setAnimationIsPlaying);

    if (propertyFlags.getHasProperty(PROP_ANIMATION_PLAYING)) {
        if (animationIsPlaying != getAnimationIsPlaying()) {
            setAnimationIsPlaying(animationIsPlaying);
        }
    }
    if (propertyFlags.getHasProperty(PROP_ANIMATION_FPS)) {
        setAnimationFPS(animationFPS);
    }
    if (propertyFlags.getHasProperty(PROP_ANIMATION_FRAME_INDEX)) {
        setAnimationFrameIndex(animationFrameIndex);
    }

    READ_ENTITY_PROPERTY(PROP_ANIMATION_SETTINGS, QString, setAnimationSettings);
    READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
    READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
    READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan);
    READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate);
    READ_ENTITY_PROPERTY(PROP_EMIT_DIRECTION, glm::vec3, setEmitDirection);
    READ_ENTITY_PROPERTY(PROP_EMIT_STRENGTH, float, setEmitStrength);
    READ_ENTITY_PROPERTY(PROP_LOCAL_GRAVITY, float, setLocalGravity);
    READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius);
    READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures);

    return bytesRead;
}
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity, EntityPropertyFlags desiredProperties) {
    EntityItemProperties results;
    if (_entityTree) {
        _entityTree->withReadLock([&] {
            EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(identity));
            if (entity) {
                if (desiredProperties.getHasProperty(PROP_POSITION) ||
                    desiredProperties.getHasProperty(PROP_ROTATION) ||
                    desiredProperties.getHasProperty(PROP_LOCAL_POSITION) ||
                    desiredProperties.getHasProperty(PROP_LOCAL_ROTATION)) {
                    // if we are explicitly getting position or rotation, we need parent information to make sense of them.
                    desiredProperties.setHasProperty(PROP_PARENT_ID);
                    desiredProperties.setHasProperty(PROP_PARENT_JOINT_INDEX);
                }

                if (desiredProperties.isEmpty()) {
                    // these are left out of EntityItem::getEntityProperties so that localPosition and localRotation
                    // don't end up in json saves, etc.  We still want them here, though.
                    EncodeBitstreamParams params; // unknown
                    desiredProperties = entity->getEntityProperties(params);
                    desiredProperties.setHasProperty(PROP_LOCAL_POSITION);
                    desiredProperties.setHasProperty(PROP_LOCAL_ROTATION);
                 }

                results = entity->getProperties(desiredProperties);

                // TODO: improve sitting points and naturalDimensions in the future,
                //       for now we've included the old sitting points model behavior for entity types that are models
                //        we've also added this hack for setting natural dimensions of models
                if (entity->getType() == EntityTypes::Model) {
                    const FBXGeometry* geometry = _entityTree->getGeometryForEntity(entity);
                    if (geometry) {
                        results.setSittingPoints(geometry->sittingPoints);
                        Extents meshExtents = geometry->getUnscaledMeshExtents();
                        results.setNaturalDimensions(meshExtents.maximum - meshExtents.minimum);
                        results.calculateNaturalPosition(meshExtents.minimum, meshExtents.maximum);
                    }
                }

            }
        });
    }

    return convertLocationToScriptSemantics(results);
}
Beispiel #6
0
void OctreeTests::propertyFlagsTests() {
    bool verbose = true;
    
    qDebug() << "FIXME: this test is broken and needs to be fixed.";
    qDebug() << "We're disabling this so that ALL_BUILD works";
    return;

    if (verbose) {
        qDebug() << "******************************************************************************************";
    }
    
    qDebug() << "OctreeTests::propertyFlagsTests()";

    {
        if (verbose) {
            qDebug() << "Test 1: EntityProperties: using setHasProperty()";
        }

        EntityPropertyFlags props;
        props.setHasProperty(PROP_VISIBLE);
        props.setHasProperty(PROP_POSITION);
        props.setHasProperty(PROP_RADIUS);
        props.setHasProperty(PROP_MODEL_URL);
        props.setHasProperty(PROP_COMPOUND_SHAPE_URL);
        props.setHasProperty(PROP_ROTATION);
    
        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 13 }));

    }

    {    
        if (verbose) {
            qDebug() << "Test 2: ExamplePropertyFlags: using setHasProperty()";
        }

        EntityPropertyFlags props2;
        props2.setHasProperty(PROP_VISIBLE);
        props2.setHasProperty(PROP_ANIMATION_URL);
        props2.setHasProperty(PROP_ANIMATION_FPS);
        props2.setHasProperty(PROP_ANIMATION_FRAME_INDEX);
        props2.setHasProperty(PROP_ANIMATION_PLAYING);
    
        QByteArray encoded = props2.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));

        if (verbose) {
            qDebug() << "Test 2b: remove flag with setHasProperty() PROP_PAUSE_SIMULATION";
        }

        encoded = props2.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 136, 30 }));
    }

    {    
        if (verbose) {
            qDebug() << "Test 3: ExamplePropertyFlags: using | operator";
        }
        
        ExamplePropertyFlags props;

        props = ExamplePropertyFlags(EXAMPLE_PROP_VISIBLE) 
                    | ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_URL)
                    | ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_FPS)
                    | ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_FRAME_INDEX)
                    | ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_PLAYING) 
                    | ExamplePropertyFlags(EXAMPLE_PROP_PAUSE_SIMULATION);
    
        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));


        if (verbose) {
            qDebug() << "Test 3b: remove flag with -= EXAMPLE_PROP_PAUSE_SIMULATION";
        }
        
        props -= EXAMPLE_PROP_PAUSE_SIMULATION;
    
        encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 136, 30 }));
    }

    {    
        if (verbose) {
            qDebug() << "Test 3c: ExamplePropertyFlags: using |= operator";
        }

        ExamplePropertyFlags props;

        props |= EXAMPLE_PROP_VISIBLE;
        props |= EXAMPLE_PROP_ANIMATION_URL;
        props |= EXAMPLE_PROP_ANIMATION_FPS;
        props |= EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props |= EXAMPLE_PROP_ANIMATION_PLAYING;
        props |= EXAMPLE_PROP_PAUSE_SIMULATION;

        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));
    }

    {    
        if (verbose) {
            qDebug() << "Test 4: ExamplePropertyFlags: using + operator";
        }

        ExamplePropertyFlags props;

        props = ExamplePropertyFlags(EXAMPLE_PROP_VISIBLE) 
                    + ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_URL)
                    + ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_FPS)
                    + ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_FRAME_INDEX)
                    + ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_PLAYING) 
                    + ExamplePropertyFlags(EXAMPLE_PROP_PAUSE_SIMULATION);
    
        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));
    }

    {    
        if (verbose) {
            qDebug() << "Test 5: ExamplePropertyFlags: using += operator";
        }
        ExamplePropertyFlags props;

        props += EXAMPLE_PROP_VISIBLE;
        props += EXAMPLE_PROP_ANIMATION_URL;
        props += EXAMPLE_PROP_ANIMATION_FPS;
        props += EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props += EXAMPLE_PROP_ANIMATION_PLAYING;
        props += EXAMPLE_PROP_PAUSE_SIMULATION;
    
        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));
    }

    {    
        if (verbose) {
            qDebug() << "Test 6: ExamplePropertyFlags: using = ... << operator";
        }
        
        ExamplePropertyFlags props;

        props = ExamplePropertyFlags(EXAMPLE_PROP_VISIBLE) 
                    << ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_URL)
                    << ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_FPS)
                    << ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_FRAME_INDEX)
                    << ExamplePropertyFlags(EXAMPLE_PROP_ANIMATION_PLAYING) 
                    << ExamplePropertyFlags(EXAMPLE_PROP_PAUSE_SIMULATION);
    
        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));
    }

    {
        if (verbose) {
            qDebug() << "Test 7: ExamplePropertyFlags: using <<= operator";
        }
        
        ExamplePropertyFlags props;

        props <<= EXAMPLE_PROP_VISIBLE;
        props <<= EXAMPLE_PROP_ANIMATION_URL;
        props <<= EXAMPLE_PROP_ANIMATION_FPS;
        props <<= EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props <<= EXAMPLE_PROP_ANIMATION_PLAYING;
        props <<= EXAMPLE_PROP_PAUSE_SIMULATION;
    
        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));
    }

    {
        if (verbose) {
            qDebug() << "Test 8: ExamplePropertyFlags: using << enum operator";
        }
        
        ExamplePropertyFlags props;

        props << EXAMPLE_PROP_VISIBLE;
        props << EXAMPLE_PROP_ANIMATION_URL;
        props << EXAMPLE_PROP_ANIMATION_FPS;
        props << EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props << EXAMPLE_PROP_ANIMATION_PLAYING;
        props << EXAMPLE_PROP_PAUSE_SIMULATION;

        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));
    }

    {
        if (verbose) {
            qDebug() << "Test 9: ExamplePropertyFlags: using << flags operator ";
        }

        ExamplePropertyFlags props;
        ExamplePropertyFlags props2;

        props << EXAMPLE_PROP_VISIBLE;
        props << EXAMPLE_PROP_ANIMATION_URL;
        props << EXAMPLE_PROP_ANIMATION_FPS;

        props2 << EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props2 << EXAMPLE_PROP_ANIMATION_PLAYING;
        props2 << EXAMPLE_PROP_PAUSE_SIMULATION;

        props << props2;

        QByteArray encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 196, 15, 2 }));
    }
  
    {
        if (verbose) {
            qDebug() << "Test 10: ExamplePropertyFlags comparison";
        }
        ExamplePropertyFlags propsA;

        if (verbose) {
            qDebug() << "!propsA:" << (!propsA) << "{ expect true }";
        }
        QCOMPARE(!propsA, true);

        propsA << EXAMPLE_PROP_VISIBLE;
        propsA << EXAMPLE_PROP_ANIMATION_URL;
        propsA << EXAMPLE_PROP_ANIMATION_FPS;
        propsA << EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        propsA << EXAMPLE_PROP_ANIMATION_PLAYING;
        propsA << EXAMPLE_PROP_PAUSE_SIMULATION;

        QCOMPARE(!propsA, false);

        ExamplePropertyFlags propsB;
        propsB << EXAMPLE_PROP_VISIBLE;
        propsB << EXAMPLE_PROP_ANIMATION_URL;
        propsB << EXAMPLE_PROP_ANIMATION_FPS;
        propsB << EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        propsB << EXAMPLE_PROP_ANIMATION_PLAYING;
        propsB << EXAMPLE_PROP_PAUSE_SIMULATION;

        if (verbose) {
            qDebug() << "propsA == propsB:" << (propsA == propsB) << "{ expect true }";
            qDebug() << "propsA != propsB:" << (propsA != propsB) << "{ expect false }";
        }
        QCOMPARE(propsA == propsB, true);
        QCOMPARE(propsA != propsB, false);
        
        if (verbose) {
            qDebug() << "AFTER propsB -= EXAMPLE_PROP_PAUSE_SIMULATION...";
        }
        
        propsB -= EXAMPLE_PROP_PAUSE_SIMULATION;

        QCOMPARE(propsA == propsB, false);
        if (verbose) {
            qDebug() << "AFTER propsB = propsA...";
        }
        propsB = propsA;
        QCOMPARE(propsA == propsB, true);
    }

    {
        if (verbose) {
            qDebug() << "Test 11: ExamplePropertyFlags testing individual properties";
        }
        ExamplePropertyFlags props;

        if (verbose) {
            qDebug() << "ExamplePropertyFlags props;";
        }
        
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "Test 11b: props.getHasProperty(EXAMPLE_PROP_VISIBLE)" << (props.getHasProperty(EXAMPLE_PROP_VISIBLE)) 
                        << "{ expect false }";
        }
        QCOMPARE(props.getHasProperty(EXAMPLE_PROP_VISIBLE), false);

        if (verbose) {
            qDebug() << "props << EXAMPLE_PROP_VISIBLE;";
        }
        props << EXAMPLE_PROP_VISIBLE;
        QCOMPARE(props.getHasProperty(EXAMPLE_PROP_VISIBLE), true);

        encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 16 }));
        if (verbose) {
            qDebug() << "props << EXAMPLE_PROP_ANIMATION_URL;";
        }
        props << EXAMPLE_PROP_ANIMATION_URL;
    
        encoded = props.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 136, 16}));
        QCOMPARE(props.getHasProperty(EXAMPLE_PROP_VISIBLE), true);

        if (verbose) {
            qDebug() << "props << ... more ...";
        }
        props << EXAMPLE_PROP_ANIMATION_FPS;
        props << EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props << EXAMPLE_PROP_ANIMATION_PLAYING;
        props << EXAMPLE_PROP_PAUSE_SIMULATION;

        encoded = props.encode();
        QCOMPARE(props.getHasProperty(EXAMPLE_PROP_VISIBLE), true);

        if (verbose) {
            qDebug() << "ExamplePropertyFlags propsB = props & EXAMPLE_PROP_VISIBLE;";
        }
        ExamplePropertyFlags propsB = props & EXAMPLE_PROP_VISIBLE;

        QCOMPARE(propsB.getHasProperty(EXAMPLE_PROP_VISIBLE), true);

        encoded = propsB.encode();
        QCOMPARE(encoded, makeQByteArray({ (char) 16 }));

        if (verbose) {
            qDebug() << "ExamplePropertyFlags propsC = ~propsB;";
        }
        ExamplePropertyFlags propsC = ~propsB;
        
        QCOMPARE(propsC.getHasProperty(EXAMPLE_PROP_VISIBLE), false);

        encoded = propsC.encode();
        if (verbose) {
            qDebug() << "propsC... encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }
    }
    
    {
        if (verbose) {
            qDebug() << "Test 12: ExamplePropertyFlags: decode tests";
        }
        ExamplePropertyFlags props;

        props << EXAMPLE_PROP_VISIBLE;
        props << EXAMPLE_PROP_ANIMATION_URL;
        props << EXAMPLE_PROP_ANIMATION_FPS;
        props << EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props << EXAMPLE_PROP_ANIMATION_PLAYING;
        props << EXAMPLE_PROP_PAUSE_SIMULATION;

        QByteArray encoded = props.encode();
        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
            qDebug() << "encoded.size()=" << encoded.size();
        }

        ExamplePropertyFlags propsDecoded;
        propsDecoded.decode(encoded);
        
        QCOMPARE(propsDecoded, props);

        QByteArray encodedAfterDecoded = propsDecoded.encode();

        QCOMPARE(encoded, encodedAfterDecoded);

        if (verbose) {
            qDebug() << "fill encoded byte array with extra garbage (as if it was bitstream with more content)";
        }
        QByteArray extraContent;
        extraContent.fill(0xbaU, 10);
        encoded.append(extraContent);

        if (verbose) {
            qDebug() << "encoded.size()=" << encoded.size() << "includes extra garbage";
        }

        ExamplePropertyFlags propsDecodedExtra;
        propsDecodedExtra.decode(encoded);
        
        QCOMPARE(propsDecodedExtra, props);

        QByteArray encodedAfterDecodedExtra = propsDecodedExtra.encode();

        if (verbose) {
            qDebug() << "encodedAfterDecodedExtra=";
            outputBufferBits((const unsigned char*)encodedAfterDecodedExtra.constData(), encodedAfterDecodedExtra.size());
        }
    }
    
    {
        if (verbose) {
            qDebug() << "Test 13: ExamplePropertyFlags: QByteArray << / >> tests";
        }
        ExamplePropertyFlags props;

        props << EXAMPLE_PROP_VISIBLE;
        props << EXAMPLE_PROP_ANIMATION_URL;
        props << EXAMPLE_PROP_ANIMATION_FPS;
        props << EXAMPLE_PROP_ANIMATION_FRAME_INDEX;
        props << EXAMPLE_PROP_ANIMATION_PLAYING;
        props << EXAMPLE_PROP_PAUSE_SIMULATION;

        if (verbose) {
            qDebug() << "testing encoded << props";
        }
        QByteArray encoded;
        encoded << props;

        if (verbose) {
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        ExamplePropertyFlags propsDecoded;
        if (verbose) {
            qDebug() << "testing encoded >> propsDecoded";
        }
        encoded >> propsDecoded;
        
        
        QCOMPARE(propsDecoded, props);
    }

    if (verbose) {
        qDebug() << "******************************************************************************************";
    }
}
Beispiel #7
0
int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
    bool wantDebug = false;

    if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) {
    
        // NOTE: This shouldn't happen. The only versions of the bit stream that didn't support split mtu buffers should
        // be handled by the model subclass and shouldn't call this routine.
        qDebug() << "EntityItem::readEntityDataFromBuffer()... "
                        "ERROR CASE...args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU";
        return 0;
    }

    // Header bytes
    //    object ID [16 bytes]
    //    ByteCountCoded(type code) [~1 byte]
    //    last edited [8 bytes]
    //    ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes]
    //    PropertyFlags<>( everything ) [1-2 bytes]
    // ~27-35 bytes...
    const int MINIMUM_HEADER_BYTES = 27;

    int bytesRead = 0;
    if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) {

        int originalLength = bytesLeftToRead;
        QByteArray originalDataBuffer((const char*)data, originalLength);

        int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;

        const unsigned char* dataAt = data;

        // id
        QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size
        _id = QUuid::fromRfc4122(encodedID);
        _creatorTokenID = UNKNOWN_ENTITY_TOKEN; // if we know the id, then we don't care about the creator token
        _newlyCreated = false;
        dataAt += encodedID.size();
        bytesRead += encodedID.size();
        
        // type
        QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size
        ByteCountCoded<quint32> typeCoder = encodedType;
        encodedType = typeCoder; // determine true length
        dataAt += encodedType.size();
        bytesRead += encodedType.size();
        quint32 type = typeCoder;
        _type = (EntityTypes::EntityType)type;

        bool overwriteLocalData = true; // assume the new content overwrites our local data

        // _created
        quint64 createdFromBuffer = 0;
        memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer));
        dataAt += sizeof(createdFromBuffer);
        bytesRead += sizeof(createdFromBuffer);
        createdFromBuffer -= clockSkew;
        
        _created = createdFromBuffer; // TODO: do we ever want to discard this???

        if (wantDebug) {
            quint64 lastEdited = getLastEdited();
            float editedAgo = getEditedAgo();
            QString agoAsString = formatSecondsElapsed(editedAgo);
            QString ageAsString = formatSecondsElapsed(getAge());
            qDebug() << "Loading entity " << getEntityItemID() << " from buffer...";
            qDebug() << "    _created =" << _created;
            qDebug() << "    age=" << getAge() << "seconds - " << ageAsString;
            qDebug() << "    lastEdited =" << lastEdited;
            qDebug() << "    ago=" << editedAgo << "seconds - " << agoAsString;
        }
        
        quint64 now = usecTimestampNow();
        quint64 lastEditedFromBuffer = 0;
        quint64 lastEditedFromBufferAdjusted = 0;

        // TODO: we could make this encoded as a delta from _created
        // _lastEdited
        memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer));
        dataAt += sizeof(lastEditedFromBuffer);
        bytesRead += sizeof(lastEditedFromBuffer);
        lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew;
        
        bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime);

        if (wantDebug) {
            qDebug() << "data from server **************** ";
            qDebug() << "      entityItemID=" << getEntityItemID();
            qDebug() << "      now=" << now;
            qDebug() << "      getLastEdited()=" << getLastEdited();
            qDebug() << "      lastEditedFromBuffer=" << lastEditedFromBuffer << " (BEFORE clockskew adjust)";
            qDebug() << "      clockSkew=" << clockSkew;
            qDebug() << "      lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted << " (AFTER clockskew adjust)";
            qDebug() << "      _lastEditedFromRemote=" << _lastEditedFromRemote 
                                        << " (our local time the last server edit we accepted)";
            qDebug() << "      _lastEditedFromRemoteInRemoteTime=" << _lastEditedFromRemoteInRemoteTime 
                                        << " (remote time the last server edit we accepted)";
            qDebug() << "      fromSameServerEdit=" << fromSameServerEdit;
        }

        bool ignoreServerPacket = false; // assume we're use this server packet
        
        // If this packet is from the same server edit as the last packet we accepted from the server
        // we probably want to use it.
        if (fromSameServerEdit) {
            // If this is from the same sever packet, then check against any local changes since we got
            // the most recent packet from this server time
            if (_lastEdited > _lastEditedFromRemote) {
                ignoreServerPacket = true;
            }
        } else {
            // If this isn't from the same sever packet, then honor our skew adjusted times...
            // If we've changed our local tree more recently than the new data from this packet
            // then we will not be changing our values, instead we just read and skip the data
            if (_lastEdited > lastEditedFromBufferAdjusted) {
                ignoreServerPacket = true;
            }
        }
        
        if (ignoreServerPacket) {
            overwriteLocalData = false;
            if (wantDebug) {
                qDebug() << "IGNORING old data from server!!! ****************";
            }
        } else {

            if (wantDebug) {
                qDebug() << "USING NEW data from server!!! ****************";
            }

            _lastEdited = lastEditedFromBufferAdjusted;
            _lastEditedFromRemote = now;
            _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer;
            
            somethingChangedNotification(); // notify derived classes that something has changed
        }

        // last updated is stored as ByteCountCoded delta from lastEdited
        QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size
        ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta;
        quint64 updateDelta = updateDeltaCoder;
        if (overwriteLocalData) {
            _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that for _lastEdited
            if (wantDebug) {
                qDebug() << "_lastUpdated=" << _lastUpdated;
                qDebug() << "_lastEdited=" << _lastEdited;
                qDebug() << "lastEditedFromBufferAdjusted=" << lastEditedFromBufferAdjusted;
            }
        }
        encodedUpdateDelta = updateDeltaCoder; // determine true length
        dataAt += encodedUpdateDelta.size();
        bytesRead += encodedUpdateDelta.size();

        // Property Flags
        QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size
        EntityPropertyFlags propertyFlags = encodedPropertyFlags;
        dataAt += propertyFlags.getEncodedLength();
        bytesRead += propertyFlags.getEncodedLength();
        
        READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, _position);

        // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS
        if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) {
            if (propertyFlags.getHasProperty(PROP_RADIUS)) {
                float fromBuffer;
                memcpy(&fromBuffer, dataAt, sizeof(fromBuffer));
                dataAt += sizeof(fromBuffer);
                bytesRead += sizeof(fromBuffer);
                if (overwriteLocalData) {
                    setRadius(fromBuffer);
                }

                if (wantDebug) {
                    qDebug() << "    readEntityDataFromBuffer() OLD FORMAT... found PROP_RADIUS";
                }

            }
        } else {
            READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, _dimensions);
            if (wantDebug) {
                qDebug() << "    readEntityDataFromBuffer() NEW FORMAT... look for PROP_DIMENSIONS";
            }
        }
        
        if (wantDebug) {
            qDebug() << "    readEntityDataFromBuffer() _dimensions:" << getDimensionsInMeters() << " in meters";
        }
                
        READ_ENTITY_PROPERTY_QUAT(PROP_ROTATION, _rotation);
        READ_ENTITY_PROPERTY(PROP_MASS, float, _mass);
        READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, _velocity);
        READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, _gravity);
        READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
        READ_ENTITY_PROPERTY(PROP_LIFETIME, float, _lifetime);
        READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript);
        READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint);
        READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, _angularVelocity);
        READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
        READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible);
        READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, _ignoreForCollisions);
        READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, _collisionsWillMove);

        if (wantDebug) {
            qDebug() << "    readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint;
            qDebug() << "    readEntityDataFromBuffer() _visible:" << _visible;
            qDebug() << "    readEntityDataFromBuffer() _ignoreForCollisions:" << _ignoreForCollisions;
            qDebug() << "    readEntityDataFromBuffer() _collisionsWillMove:" << _collisionsWillMove;
        }

        bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);

        recalculateCollisionShape();
    }
    return bytesRead;
}
Beispiel #8
0
void OctreeTests::propertyFlagsTests(bool verbose) {
    int testsTaken = 0;
    int testsPassed = 0;
    int testsFailed = 0;

    if (verbose) {
        qDebug() << "******************************************************************************************";
    }
    
    qDebug() << "OctreeTests::propertyFlagsTests()";

    {
        if (verbose) {
            qDebug() << "Test 1: EntityProperties: using setHasProperty()";
        }
        testsTaken++;

        EntityPropertyFlags props;
        props.setHasProperty(PROP_VISIBLE);
        props.setHasProperty(PROP_POSITION);
        props.setHasProperty(PROP_RADIUS);
        props.setHasProperty(PROP_MODEL_URL);
        props.setHasProperty(PROP_ROTATION);
    
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }
        
        char expectedBytes[] = { 31 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 1: EntityProperties: using setHasProperty()";
        }

    }

    {    
        if (verbose) {
            qDebug() << "Test 2: ParticlePropertyFlags: using setHasProperty()";
        }
        testsTaken++;

        ParticlePropertyFlags props2;
        props2.setHasProperty(PARTICLE_PROP_VISIBLE);
        props2.setHasProperty(PARTICLE_PROP_ANIMATION_URL);
        props2.setHasProperty(PARTICLE_PROP_ANIMATION_FPS);
        props2.setHasProperty(PARTICLE_PROP_ANIMATION_FRAME_INDEX);
        props2.setHasProperty(PARTICLE_PROP_ANIMATION_PLAYING);
        props2.setHasProperty(PARTICLE_PROP_PAUSE_SIMULATION);
    
        QByteArray encoded = props2.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 2: ParticlePropertyFlags: using setHasProperty()";
        }

        
        if (verbose) {
            qDebug() << "Test 2b: remove flag with setHasProperty() PARTICLE_PROP_PAUSE_SIMULATION";
        }
        testsTaken++;

        props2.setHasProperty(PARTICLE_PROP_PAUSE_SIMULATION, false);
    
        encoded = props2.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytesB[] = { (char)136, (char)30 };
        QByteArray expectedResultB(expectedBytesB, sizeof(expectedBytesB)/sizeof(expectedBytesB[0]));
        
        if (encoded == expectedResultB) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 2b: remove flag with setHasProperty() PARTICLE_PROP_PAUSE_SIMULATION";
        }
    }

    {    
        if (verbose) {
            qDebug() << "Test 3: ParticlePropertyFlags: using | operator";
        }
        testsTaken++;
        
        ParticlePropertyFlags props;

        props = ParticlePropertyFlags(PARTICLE_PROP_VISIBLE) 
                    | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_URL)
                    | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FPS)
                    | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FRAME_INDEX)
                    | ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_PLAYING) 
                    | ParticlePropertyFlags(PARTICLE_PROP_PAUSE_SIMULATION);
    
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }
        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 3: ParticlePropertyFlags: using | operator";
        }


        if (verbose) {
            qDebug() << "Test 3b: remove flag with -= PARTICLE_PROP_PAUSE_SIMULATION";
        }
        testsTaken++;
        
        props -= PARTICLE_PROP_PAUSE_SIMULATION;
    
        encoded = props.encode();
    
        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }
        
        char expectedBytesB[] = { (char)136, (char)30 };
        QByteArray expectedResultB(expectedBytesB, sizeof(expectedBytesB)/sizeof(expectedBytesB[0]));
        
        if (encoded == expectedResultB) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 3b: remove flag with -= PARTICLE_PROP_PAUSE_SIMULATION";
        }
        
    }

    {    
        if (verbose) {
            qDebug() << "Test 3c: ParticlePropertyFlags: using |= operator";
        }
        testsTaken++;

        ParticlePropertyFlags props;

        props |= PARTICLE_PROP_VISIBLE;
        props |= PARTICLE_PROP_ANIMATION_URL;
        props |= PARTICLE_PROP_ANIMATION_FPS;
        props |= PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props |= PARTICLE_PROP_ANIMATION_PLAYING;
        props |= PARTICLE_PROP_PAUSE_SIMULATION;

        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - 3c: ParticlePropertyFlags: using |= operator";
        }
    }

    {    
        if (verbose) {
            qDebug() << "Test 4: ParticlePropertyFlags: using + operator";
        }
        testsTaken++;

        ParticlePropertyFlags props;

        props = ParticlePropertyFlags(PARTICLE_PROP_VISIBLE) 
                    + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_URL)
                    + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FPS)
                    + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FRAME_INDEX)
                    + ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_PLAYING) 
                    + ParticlePropertyFlags(PARTICLE_PROP_PAUSE_SIMULATION);
    
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 4: ParticlePropertyFlags: using + operator";
        }
    }

    {    
        if (verbose) {
            qDebug() << "Test 5: ParticlePropertyFlags: using += operator";
        }
        testsTaken++;
        ParticlePropertyFlags props;

        props += PARTICLE_PROP_VISIBLE;
        props += PARTICLE_PROP_ANIMATION_URL;
        props += PARTICLE_PROP_ANIMATION_FPS;
        props += PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props += PARTICLE_PROP_ANIMATION_PLAYING;
        props += PARTICLE_PROP_PAUSE_SIMULATION;
    
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 5: ParticlePropertyFlags: using += operator";
        }
    }

    {    
        if (verbose) {
            qDebug() << "Test 6: ParticlePropertyFlags: using = ... << operator";
        }
        testsTaken++;
        
        ParticlePropertyFlags props;

        props = ParticlePropertyFlags(PARTICLE_PROP_VISIBLE) 
                    << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_URL)
                    << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FPS)
                    << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_FRAME_INDEX)
                    << ParticlePropertyFlags(PARTICLE_PROP_ANIMATION_PLAYING) 
                    << ParticlePropertyFlags(PARTICLE_PROP_PAUSE_SIMULATION);
    
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 6: ParticlePropertyFlags: using = ... << operator";
        }
    }

    {
        if (verbose) {
            qDebug() << "Test 7: ParticlePropertyFlags: using <<= operator";
        }
        testsTaken++;
        
        ParticlePropertyFlags props;

        props <<= PARTICLE_PROP_VISIBLE;
        props <<= PARTICLE_PROP_ANIMATION_URL;
        props <<= PARTICLE_PROP_ANIMATION_FPS;
        props <<= PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props <<= PARTICLE_PROP_ANIMATION_PLAYING;
        props <<= PARTICLE_PROP_PAUSE_SIMULATION;
    
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 7: ParticlePropertyFlags: using <<= operator";
        }
    }

    {
        if (verbose) {
            qDebug() << "Test 8: ParticlePropertyFlags: using << enum operator";
        }
        testsTaken++;
        
        ParticlePropertyFlags props;

        props << PARTICLE_PROP_VISIBLE;
        props << PARTICLE_PROP_ANIMATION_URL;
        props << PARTICLE_PROP_ANIMATION_FPS;
        props << PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props << PARTICLE_PROP_ANIMATION_PLAYING;
        props << PARTICLE_PROP_PAUSE_SIMULATION;

        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 8: ParticlePropertyFlags: using << enum operator";
        }
    }

    {
        if (verbose) {
            qDebug() << "Test 9: ParticlePropertyFlags: using << flags operator ";
        }
        testsTaken++;

        ParticlePropertyFlags props;
        ParticlePropertyFlags props2;

        props << PARTICLE_PROP_VISIBLE;
        props << PARTICLE_PROP_ANIMATION_URL;
        props << PARTICLE_PROP_ANIMATION_FPS;

        props2 << PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props2 << PARTICLE_PROP_ANIMATION_PLAYING;
        props2 << PARTICLE_PROP_PAUSE_SIMULATION;

        props << props2;

        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { (char)196, (char)15, (char)2 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 9: ParticlePropertyFlags: using << flags operator";
        }
    }
  
    {
        if (verbose) {
            qDebug() << "Test 10: ParticlePropertyFlags comparison";
        }
        ParticlePropertyFlags propsA;

        if (verbose) {
            qDebug() << "!propsA:" << (!propsA) << "{ expect true }";
        }
        testsTaken++;
        bool resultA = (!propsA);
        bool expectedA = true;
        if (resultA == expectedA) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 10a: ParticlePropertyFlags comparison, uninitialized !propsA";
        }

        propsA << PARTICLE_PROP_VISIBLE;
        propsA << PARTICLE_PROP_ANIMATION_URL;
        propsA << PARTICLE_PROP_ANIMATION_FPS;
        propsA << PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        propsA << PARTICLE_PROP_ANIMATION_PLAYING;
        propsA << PARTICLE_PROP_PAUSE_SIMULATION;

        if (verbose) {
            qDebug() << "!propsA:" << (!propsA) << "{ expect false }";
        }
        testsTaken++;
        bool resultB = (!propsA);
        bool expectedB = false;
        if (resultB == expectedB) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 10b: ParticlePropertyFlags comparison, initialized !propsA";
        }

        ParticlePropertyFlags propsB;
        propsB << PARTICLE_PROP_VISIBLE;
        propsB << PARTICLE_PROP_ANIMATION_URL;
        propsB << PARTICLE_PROP_ANIMATION_FPS;
        propsB << PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        propsB << PARTICLE_PROP_ANIMATION_PLAYING;
        propsB << PARTICLE_PROP_PAUSE_SIMULATION;

        if (verbose) {
            qDebug() << "propsA == propsB:" << (propsA == propsB) << "{ expect true }";
            qDebug() << "propsA != propsB:" << (propsA != propsB) << "{ expect false }";
        }
        testsTaken++;
        bool resultC = (propsA == propsB);
        bool expectedC = true;
        if (resultC == expectedC) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 10c: ParticlePropertyFlags comparison, propsA == propsB";
        }

        testsTaken++;
        bool resultD = (propsA != propsB);
        bool expectedD = false;
        if (resultD == expectedD) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 10d: ParticlePropertyFlags comparison, propsA != propsB";
        }
        
        if (verbose) {
            qDebug() << "AFTER propsB -= PARTICLE_PROP_PAUSE_SIMULATION...";
        }
        
        propsB -= PARTICLE_PROP_PAUSE_SIMULATION;

        if (verbose) {
            qDebug() << "propsA == propsB:" << (propsA == propsB) << "{ expect false }";
            qDebug() << "propsA != propsB:" << (propsA != propsB) << "{ expect true }";
        }
        testsTaken++;
        bool resultE = (propsA == propsB);
        bool expectedE = false;
        if (resultE == expectedE) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 10e: ParticlePropertyFlags comparison, AFTER propsB -= PARTICLE_PROP_PAUSE_SIMULATION";
        }
        
        if (verbose) {
            qDebug() << "AFTER propsB = propsA...";
        }
        propsB = propsA;
        if (verbose) {
            qDebug() << "propsA == propsB:" << (propsA == propsB) << "{ expect true }";
            qDebug() << "propsA != propsB:" << (propsA != propsB) << "{ expect false }";
        }
        testsTaken++;
        bool resultF = (propsA == propsB);
        bool expectedF = true;
        if (resultF == expectedF) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 10f: ParticlePropertyFlags comparison, AFTER propsB = propsA";
        }
    }

    {
        if (verbose) {
            qDebug() << "Test 11: ParticlePropertyFlags testing individual properties";
        }
        ParticlePropertyFlags props;

        if (verbose) {
            qDebug() << "ParticlePropertyFlags props;";
        }
        
        QByteArray encoded = props.encode();

        if (verbose) {
            qDebug() << "props... encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        char expectedBytes[] = { 0 };
        QByteArray expectedResult(expectedBytes, sizeof(expectedBytes)/sizeof(expectedBytes[0]));
        
        testsTaken++;
        if (encoded == expectedResult) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11a: ParticlePropertyFlags testing individual properties";
        }


        if (verbose) {
            qDebug() << "Test 11b: props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) 
                        << "{ expect false }";
        }
        testsTaken++;
        bool resultB = props.getHasProperty(PARTICLE_PROP_VISIBLE);
        bool expectedB = false;
        if (resultB == expectedB) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11b: props.getHasProperty(PARTICLE_PROP_VISIBLE)";
        }

        if (verbose) {
            qDebug() << "props << PARTICLE_PROP_VISIBLE;";
        }
        props << PARTICLE_PROP_VISIBLE;
        testsTaken++;
        bool resultC = props.getHasProperty(PARTICLE_PROP_VISIBLE);
        bool expectedC = true;
        if (resultC == expectedC) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11c: props.getHasProperty(PARTICLE_PROP_VISIBLE) after props << PARTICLE_PROP_VISIBLE";
        }

        encoded = props.encode();

        if (verbose) {
            qDebug() << "props... encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
            qDebug() << "props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) 
                            << "{ expect true }";
        }

        char expectedBytesC[] = { 16 };
        QByteArray expectedResultC(expectedBytesC, sizeof(expectedBytesC)/sizeof(expectedBytesC[0]));
        
        testsTaken++;
        if (encoded == expectedResultC) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11c: ParticlePropertyFlags testing individual properties";
        }

        if (verbose) {
            qDebug() << "props << PARTICLE_PROP_ANIMATION_URL;";
        }
        props << PARTICLE_PROP_ANIMATION_URL;

        encoded = props.encode();
        if (verbose) {
            qDebug() << "props... encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
            qDebug() << "props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) 
                            << "{ expect true }";
        }
        char expectedBytesD[] = { (char)136, (char)16 };
        QByteArray expectedResultD(expectedBytesD, sizeof(expectedBytesD)/sizeof(expectedBytesD[0]));
        
        testsTaken++;
        if (encoded == expectedResultD) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11d: ParticlePropertyFlags testing individual properties";
        }
        testsTaken++;
        bool resultE = props.getHasProperty(PARTICLE_PROP_VISIBLE);
        bool expectedE = true;
        if (resultE == expectedE) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11e: props.getHasProperty(PARTICLE_PROP_VISIBLE) after props << PARTICLE_PROP_ANIMATION_URL";
        }


        if (verbose) {
            qDebug() << "props << ... more ...";
        }
        props << PARTICLE_PROP_ANIMATION_FPS;
        props << PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props << PARTICLE_PROP_ANIMATION_PLAYING;
        props << PARTICLE_PROP_PAUSE_SIMULATION;

        encoded = props.encode();
        if (verbose) {
            qDebug() << "props... encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
            qDebug() << "props.getHasProperty(PARTICLE_PROP_VISIBLE)" << (props.getHasProperty(PARTICLE_PROP_VISIBLE)) 
                            << "{ expect true }";
        }
        testsTaken++;
        bool resultF = props.getHasProperty(PARTICLE_PROP_VISIBLE);
        bool expectedF = true;
        if (resultF == expectedF) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11f: props.getHasProperty(PARTICLE_PROP_VISIBLE) after props << more";
        }

        if (verbose) {
            qDebug() << "ParticlePropertyFlags propsB = props & PARTICLE_PROP_VISIBLE;";
        }
        ParticlePropertyFlags propsB = props & PARTICLE_PROP_VISIBLE;

        if (verbose) {
            qDebug() << "propsB.getHasProperty(PARTICLE_PROP_VISIBLE)" << (propsB.getHasProperty(PARTICLE_PROP_VISIBLE)) 
                        << "{ expect true }";
        }
        testsTaken++;
        bool resultG = propsB.getHasProperty(PARTICLE_PROP_VISIBLE);
        bool expectedG = true;
        if (resultG == expectedG) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11g: propsB = props & PARTICLE_PROP_VISIBLE";
        }

        encoded = propsB.encode();
        if (verbose) {
            qDebug() << "propsB... encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }
        char expectedBytesH[] = { 16 };
        QByteArray expectedResultH(expectedBytesC, sizeof(expectedBytesH)/sizeof(expectedBytesH[0]));
        
        testsTaken++;
        if (encoded == expectedResultH) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11h: ParticlePropertyFlags testing individual properties";
        }

        if (verbose) {
            qDebug() << "ParticlePropertyFlags propsC = ~propsB;";
        }
        ParticlePropertyFlags propsC = ~propsB;
        
        if (verbose) {
            qDebug() << "propsC.getHasProperty(PARTICLE_PROP_VISIBLE)" << (propsC.getHasProperty(PARTICLE_PROP_VISIBLE))
                        << "{ expect false }";
        }
        testsTaken++;
        bool resultI = propsC.getHasProperty(PARTICLE_PROP_VISIBLE);
        bool expectedI = false;
        if (resultI == expectedI) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 11i: propsC = ~propsB";
        }

        encoded = propsC.encode();
        if (verbose) {
            qDebug() << "propsC... encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }
    }
    
    {
        if (verbose) {
            qDebug() << "Test 12: ParticlePropertyFlags: decode tests";
        }
        ParticlePropertyFlags props;

        props << PARTICLE_PROP_VISIBLE;
        props << PARTICLE_PROP_ANIMATION_URL;
        props << PARTICLE_PROP_ANIMATION_FPS;
        props << PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props << PARTICLE_PROP_ANIMATION_PLAYING;
        props << PARTICLE_PROP_PAUSE_SIMULATION;

        QByteArray encoded = props.encode();
        if (verbose) {
            qDebug() << "encoded=";
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
            qDebug() << "encoded.size()=" << encoded.size();
        }

        ParticlePropertyFlags propsDecoded;
        propsDecoded.decode(encoded);
        
        if (verbose) {
            qDebug() << "propsDecoded == props:" << (propsDecoded == props) << "{ expect true }";
        }
        testsTaken++;
        bool resultA = (propsDecoded == props);
        bool expectedA = true;
        if (resultA == expectedA) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 12a: propsDecoded == props";
        }

        QByteArray encodedAfterDecoded = propsDecoded.encode();

        if (verbose) {
            qDebug() << "encodedAfterDecoded=";
            outputBufferBits((const unsigned char*)encodedAfterDecoded.constData(), encodedAfterDecoded.size());
        }
        testsTaken++;
        bool resultB = (encoded == encodedAfterDecoded);
        bool expectedB = true;
        if (resultB == expectedB) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 12b: (encoded == encodedAfterDecoded)";
        }

        if (verbose) {
            qDebug() << "fill encoded byte array with extra garbage (as if it was bitstream with more content)";
        }
        QByteArray extraContent;
        extraContent.fill(0xba, 10);
        encoded.append(extraContent);

        if (verbose) {
            qDebug() << "encoded.size()=" << encoded.size() << "includes extra garbage";
        }

        ParticlePropertyFlags propsDecodedExtra;
        propsDecodedExtra.decode(encoded);
        
        if (verbose) {
            qDebug() << "propsDecodedExtra == props:" << (propsDecodedExtra == props) << "{ expect true }";
        }
        testsTaken++;
        bool resultC = (propsDecodedExtra == props);
        bool expectedC = true;
        if (resultC == expectedC) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 12c: (propsDecodedExtra == props)";
        }

        QByteArray encodedAfterDecodedExtra = propsDecodedExtra.encode();

        if (verbose) {
            qDebug() << "encodedAfterDecodedExtra=";
            outputBufferBits((const unsigned char*)encodedAfterDecodedExtra.constData(), encodedAfterDecodedExtra.size());
        }
    }
    
    {
        if (verbose) {
            qDebug() << "Test 13: ParticlePropertyFlags: QByteArray << / >> tests";
        }
        ParticlePropertyFlags props;

        props << PARTICLE_PROP_VISIBLE;
        props << PARTICLE_PROP_ANIMATION_URL;
        props << PARTICLE_PROP_ANIMATION_FPS;
        props << PARTICLE_PROP_ANIMATION_FRAME_INDEX;
        props << PARTICLE_PROP_ANIMATION_PLAYING;
        props << PARTICLE_PROP_PAUSE_SIMULATION;

        if (verbose) {
            qDebug() << "testing encoded << props";
        }
        QByteArray encoded;
        encoded << props;

        if (verbose) {
            outputBufferBits((const unsigned char*)encoded.constData(), encoded.size());
        }

        ParticlePropertyFlags propsDecoded;
        if (verbose) {
            qDebug() << "testing encoded >> propsDecoded";
        }
        encoded >> propsDecoded;

        if (verbose) {
            qDebug() << "propsDecoded==props" << (propsDecoded==props);
        }

        testsTaken++;
        bool resultA = (propsDecoded == props);
        bool expectedA = true;
        if (resultA == expectedA) {
            testsPassed++;
        } else {
            testsFailed++;
            qDebug() << "FAILED - Test 13: ParticlePropertyFlags: QByteArray << / >> tests";
        }
    }

    qDebug() << "   tests passed:" << testsPassed << "out of" << testsTaken;
    if (verbose) {
        qDebug() << "******************************************************************************************";
    }
}
// TODO: 
//   how to handle lastEdited?
//   how to handle lastUpdated?
//   consider handling case where no properties are included... we should just ignore this packet...
//
// TODO: Right now, all possible properties for all subclasses are handled here. Ideally we'd prefer
//       to handle this in a more generic way. Allowing subclasses of EntityItem to register their properties
//
// TODO: There's a lot of repeated patterns in the code below to handle each property. It would be nice if the property
//       registration mechanism allowed us to collapse these repeated sections of code into a single implementation that
//       utilized the registration table to shorten up and simplify this code.
//
// TODO: Implement support for paged properties, spanning MTU, and custom properties
//
// TODO: Implement support for script and visible properties.
//
bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes,
                        EntityItemID& entityID, EntityItemProperties& properties) {
    bool valid = false;

    const unsigned char* dataAt = data;
    processedBytes = 0;

    // the first part of the data is an octcode, this is a required element of the edit packet format, but we don't
    // actually use it, we do need to skip it and read to the actual data we care about.
    int octets = numberOfThreeBitSectionsInCode(data);
    int bytesToReadOfOctcode = bytesRequiredForCodeLength(octets);

    // we don't actually do anything with this octcode...
    dataAt += bytesToReadOfOctcode;
    processedBytes += bytesToReadOfOctcode;
    
    // Edit packets have a last edited time stamp immediately following the octcode.
    // NOTE: the edit times have been set by the editor to match out clock, so we don't need to adjust
    // these times for clock skew at this point.
    quint64 lastEdited;
    memcpy(&lastEdited, dataAt, sizeof(lastEdited));
    dataAt += sizeof(lastEdited);
    processedBytes += sizeof(lastEdited);
    properties.setLastEdited(lastEdited);

    // NOTE: We intentionally do not send "created" times in edit messages. This is because:
    //   1) if the edit is to an existing entity, the created time can not be changed
    //   2) if the edit is to a new entity, the created time is the last edited time

    // encoded id
    QByteArray encodedID((const char*)dataAt, NUM_BYTES_RFC4122_UUID); // maximum possible size
    QUuid editID = QUuid::fromRfc4122(encodedID);
    dataAt += encodedID.size();
    processedBytes += encodedID.size();

    bool isNewEntityItem = (editID == NEW_ENTITY);

    if (isNewEntityItem) {
        // If this is a NEW_ENTITY, then we assume that there's an additional uint32_t creatorToken, that
        // we want to send back to the creator as an map to the actual id

        QByteArray encodedToken((const char*)dataAt, (bytesToRead - processedBytes));
        ByteCountCoded<quint32> tokenCoder = encodedToken;
        quint32 creatorTokenID = tokenCoder;
        encodedToken = tokenCoder; // determine true bytesToRead
        dataAt += encodedToken.size();
        processedBytes += encodedToken.size();

        //newEntityItem.setCreatorTokenID(creatorTokenID);
        //newEntityItem._newlyCreated = true;
        
        entityID.id = NEW_ENTITY;
        entityID.creatorTokenID = creatorTokenID;
        entityID.isKnownID = false;

        valid = true;

        // created time is lastEdited time
        properties.setCreated(lastEdited);
    } else {
        entityID.id = editID;
        entityID.creatorTokenID = UNKNOWN_ENTITY_TOKEN;
        entityID.isKnownID = true;
        valid = true;

        // created time is lastEdited time
        properties.setCreated(USE_EXISTING_CREATED_TIME);
    }

    // Entity Type...
    QByteArray encodedType((const char*)dataAt, (bytesToRead - processedBytes));
    ByteCountCoded<quint32> typeCoder = encodedType;
    quint32 entityTypeCode = typeCoder;
    properties.setType((EntityTypes::EntityType)entityTypeCode);
    encodedType = typeCoder; // determine true bytesToRead
    dataAt += encodedType.size();
    processedBytes += encodedType.size();

    // Update Delta - when was this item updated relative to last edit... this really should be 0
    // TODO: Should we get rid of this in this in edit packets, since this has to always be 0?
    // TODO: do properties need to handle lastupdated???

    // last updated is stored as ByteCountCoded delta from lastEdited
    QByteArray encodedUpdateDelta((const char*)dataAt, (bytesToRead - processedBytes));
    ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta;
    encodedUpdateDelta = updateDeltaCoder; // determine true bytesToRead
    dataAt += encodedUpdateDelta.size();
    processedBytes += encodedUpdateDelta.size();

    // TODO: Do we need this lastUpdated?? We don't seem to use it.
    //quint64 updateDelta = updateDeltaCoder;
    //quint64 lastUpdated = lastEdited + updateDelta; // don't adjust for clock skew since we already did that for lastEdited
    
    // Property Flags...
    QByteArray encodedPropertyFlags((const char*)dataAt, (bytesToRead - processedBytes));
    EntityPropertyFlags propertyFlags = encodedPropertyFlags;
    dataAt += propertyFlags.getEncodedLength();
    processedBytes += propertyFlags.getEncodedLength();

    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions);  // NOTE: PROP_RADIUS obsolete
    READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(PROP_ROTATION, setRotation);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MASS, float, setMass);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime);
    READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_SCRIPT,setScript);
    READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_COLOR, setColor);
    READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MODEL_URL, setModelURL);
    READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_URL, setAnimationURL);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FPS, float, setAnimationFPS);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_PLAYING, bool, setAnimationIsPlaying);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping);
    READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible);

    return valid;
}