//======================================================================
        //incoming()
        //
        //Return the incoming tangent to the curve at key1.  The value returned
        //for the BEZ2 case is used when extrapolating a linear post behavior.
        //======================================================================
        static float Incoming(EnvelopeKey key0, EnvelopeKey key1, EnvelopeKey keyn1)
        {
            float a, b, d, t, _in;

            switch (key1.Shape)
            {
                case KeyShape.ID_LINE:
                {
                    d = key1.Value - key0.Value;
                    if (keyn1 != null)
                    {
                        t = (key1.Time - key0.Time) / (keyn1.Time - key0.Time);
                        _in = t * (keyn1.Value - key1.Value + d);
                    }
                    else
                        _in = d;
                    break;
                }

                case KeyShape.ID_TCB:
                {
                    a = (1.0f - key1.Tension)
                        * (1.0f - key1.Continuity)
                        * (1.0f + key1.Bias);
                    b = (1.0f - key1.Tension)
                        * (1.0f + key1.Continuity)
                        * (1.0f - key1.Bias);
                    d = key1.Value - key0.Value;

                    if (keyn1 != null)
                    {
                        t = (key1.Time - key0.Time) / (keyn1.Time - key0.Time);
                        _in = t * (b * (keyn1.Value - key1.Value) + a * d);
                    }
                    else
                        _in = a * d;
                    break;
                }

                case KeyShape.ID_BEZI:
                case KeyShape.ID_HERM:
                {
                    _in = key1.param[0];
                    if (keyn1 != null)
                        _in *= (key1.Time - key0.Time) / (keyn1.Time - key0.Time);
                    break;
                }

                case KeyShape.ID_BEZ2:
                {
                    _in = key1.param[1] * (key1.Time - key0.Time);
                    if (Math.Abs(key1.param[0]) > 1e-5f)
                        _in /= key1.param[0];
                    else
                        _in *= 1e5f;
                    break;
                }

                case KeyShape.ID_STEP:
                default:
                {
                    _in = 0.0f;
                    break;
                }
            }

            return _in;
        }
        //======================================================================
        //bez2()
        //
        //Interpolate the value of a BEZ2 curve.
        //======================================================================
        static float Bez2(EnvelopeKey key0, EnvelopeKey key1, float time)
        {
            float x, y, t, t0 = 0.0f, t1 = 1.0f;

            if (key0.Shape == KeyShape.ID_BEZ2)
                x = key0.Time + key0.param[2];
            else
                x = key0.Time + (key1.Time - key0.Time) / 3.0f;

            t = Bez2_time(key0.Time, x, key1.Time + key1.param[0], key1.Time, time, ref t0, ref t1);

            if (key0.Shape == KeyShape.ID_BEZ2)
                y = key0.Value + key0.param[3];
            else
                y = key0.Value + key0.param[1] / 3.0f;

            return Bezier(key0.Value, y, key1.param[1] + key1.Value, key1.Value, t);
        }
        //======================================================================
        // AddTextureVelocity()
        //
        // Add a triple of envelopes to simulate the old texture velocity
        // parameters.
        //======================================================================
        static uint AddTextureVelocity(float[] pos, float[] vel, List<Envelope> elist)
        {
            Envelope env = null;
            for (var i = 0; i < 3; i++)
            {
                env = new Envelope();
                var key0 = new EnvelopeKey(0,					// time
                                    pos[i]);					// value
                var key1 = new EnvelopeKey(1,					// time
                                     pos[i] + vel[i] * 30.0f);	// value

                key0.Shape = key1.Shape = KeyShape.ID_LINE;

                env.Index = (uint)(elist.Count + 1); // Q: shouldn't this be + 0?
                env.Type = 0x0301 + i;
                env.Name = "Position." + i;
                env.Keys.Add(key0);
                env.Keys.Add(key1);
                env.PreBehavior		= EvalType.BEH_LINEAR;
                env.PostBehavior	= EvalType.BEH_LINEAR;

                elist.Add(env);
            }

            return env.Index - 2; // Q: why the -2?
        }
        public static Envelope ReadEnvelope(ChunkReader reader)
        {
            var env = new Envelope(); // allocate the Envelope structure
            env.Index = reader.ReadVariableLengthIndex(); // index

            EnvelopeKey lastKey = null;
            while (reader.BytesLeft > 0)
            {
                // process subchunks as they're encountered
                var id = reader.ReadID<EnvelopeType>();
                var sz = reader.ReadUInt16();
                sz += (ushort)(sz & 1);

                using (var subChunkReader = reader.GetSubChunk(sz))
                {
                    switch (id)
                    {
                        case EnvelopeType.ID_TYPE:
                        {
                            env.Type = subChunkReader.ReadUInt16();
                            break;
                        }

                        case EnvelopeType.ID_NAME:
                        {
                            env.Name = subChunkReader.ReadString();
                            break;
                        }

                        case EnvelopeType.ID_PRE:
                        {
                            env.PreBehavior = (EvalType)subChunkReader.ReadUInt16();
                            break;
                        }

                        case EnvelopeType.ID_POST:
                        {
                            env.PostBehavior = (EvalType)subChunkReader.ReadUInt16();
                            break;
                        }

                        case EnvelopeType.ID_KEY:
                        {
                            lastKey = new EnvelopeKey(
                                                subChunkReader.ReadSingle(),	// time
                                                subChunkReader.ReadSingle()		// value
                                                );
                            env.Keys.Add(lastKey);

                            //TODO: not sort all the time
                            env.Keys.Sort((Comparison<EnvelopeKey>)delegate(EnvelopeKey k1, EnvelopeKey k2)
                            {
                                return k1.Time > k2.Time ? 1 : k1.Time < k2.Time ? -1 : 0;
                            });
                            break;
                        }

                        case EnvelopeType.ID_SPAN:
                        {
                            if (lastKey == null) // We should've encountered an ID_KEY before an ID_SPAN
                                throw new Exception("Key not defined"); //TODO: make proper exception class

                            lastKey.Shape = subChunkReader.ReadID<KeyShape>();
                            switch (lastKey.Shape)
                            {
                                case KeyShape.ID_TCB:
                                {
                                    lastKey.Tension		= subChunkReader.ReadSingle();
                                    lastKey.Continuity	= subChunkReader.ReadSingle();
                                    lastKey.Bias		= subChunkReader.ReadSingle();
                                    break;
                                }

                                case KeyShape.ID_BEZI:
                                case KeyShape.ID_HERM:
                                case KeyShape.ID_BEZ2:
                                {
                                    Array.Clear(lastKey.param, 0, lastKey.param.Length);
                                    int i = 0;
                                    while (i < 4 && subChunkReader.BytesLeft > 0)
                                    {
                                        lastKey.param[i] = subChunkReader.ReadSingle();
                                        i++;
                                    }
                                    break;
                                }

                                case KeyShape.ID_LINE:
                                    break;

                                default:
                                    Console.WriteLine("Unknown envelope span shape type " + reader.GetIDString((uint)lastKey.Shape));
                                    break;
                            }
                            break;
                        }

                        case EnvelopeType.ID_CHAN:
                        {
                            var plug = new LightwavePlugin();
                            plug.name	= subChunkReader.ReadString();
                            plug.flags	= subChunkReader.ReadUInt16();
                            plug.data	= subChunkReader.ReadBytes((uint)reader.BytesLeft);
                            env.ChannelFilters.Add(plug);
                            break;
                        }

                        default:
                            Console.WriteLine("Unknown envelope type " + reader.GetIDString((uint)id));
                            break;
                    }
                }
            }

            return env;
        }
        //======================================================================
        //outgoing()
        //
        //Return the outgoing tangent to the curve at key0.  The value returned
        //for the BEZ2 case is used when extrapolating a linear pre behavior and
        //when interpolating a non-BEZ2 span.
        //======================================================================
        static float Outgoing(EnvelopeKey keyp0, EnvelopeKey key0, EnvelopeKey key1)
        {
            float a, b, d, t, _out;

            switch (key0.Shape)
            {
                case KeyShape.ID_TCB:
                    {
                        a = (1.0f - key0.Tension)
                            * (1.0f + key0.Continuity)
                            * (1.0f + key0.Bias);
                        b = (1.0f - key0.Tension)
                            * (1.0f - key0.Continuity)
                            * (1.0f - key0.Bias);
                        d = key1.Value - key0.Value;

                        if (keyp0 != null)
                        {
                            t = (key1.Time - key0.Time) / (key1.Time - keyp0.Time);
                            _out = t * (a * (key0.Value - keyp0.Value) + b * d);
                        }
                        else
                            _out = b * d;
                        break;
                    }

                case KeyShape.ID_LINE:
                    {
                        d = key1.Value - key0.Value;
                        if (keyp0 != null)
                        {
                            t = (key1.Time - key0.Time) / (key1.Time - keyp0.Time);
                            _out = t * (key0.Value - keyp0.Value + d);
                        }
                        else
                            _out = d;
                        break;
                    }

                case KeyShape.ID_BEZI:
                case KeyShape.ID_HERM:
                    {
                        _out = key0.param[1];
                        if (keyp0 != null)
                            _out *= (key1.Time - key0.Time) / (key1.Time - keyp0.Time);
                        break;
                    }

                case KeyShape.ID_BEZ2:
                    {
                        _out = key0.param[3] * (key1.Time - key0.Time);
                        if (Math.Abs(key0.param[2]) > 1e-5f)
                            _out /= key0.param[2];
                        else
                            _out *= 1e5f;
                        break;
                    }

                case KeyShape.ID_STEP:
                default:
                    {
                        _out = 0.0f;
                        break;
                    }
            }

            return _out;
        }