Andrew Meadowsarchive remaining JS scriptsc0b446c10 years agoHistory
1221 lines · 50.0 KB · JavaScript
RawDownload
//// platform.js//// Created by Seiji Emery on 8/19/15// Copyright 2015 High Fidelity, Inc.//// Entity stress test / procedural demo.// Spawns a platform under your avatar made up of randomly sized and colored boxes or spheres. The platform follows your avatar// around, and comes with a UI to update the platform's properties (radius, entity density, color distribution, etc) in real time.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html//  // UI and debug console implemented using uiwidgets / 2d overlaysScript.include("../../libraries/uiwidgets.js");if (typeof(UI) === 'undefined') {   // backup link in case the user downloaded this somewhere    print("Missing library script -- loading from public.highfidelity.io");    Script.include('http://public.highfidelity.io/scripts/libraries/uiwidgets.js');    if (typeof(UI) === 'undefined') {        print("Cannot load UIWidgets library -- check your internet connection", COLORS.RED);        throw new Error("Could not load uiwidgets.js");    }} // Platform script(function () {var SCRIPT_NAME = "platform.js";var USE_DEBUG_LOG = true;   // Turns on the 2dOverlay-based debug log. If false, just redirects to print.var NUM_DEBUG_LOG_LINES = 10;var LOG_ENTITY_CREATION_MESSAGES = false;   // detailed debugging (init)var LOG_UPDATE_STATUS_MESSAGES = false;     // detailed debugging (startup) var MAX_UPDATE_INTERVAL = 0.2;  // restrict to 5 updates / secvar AVATAR_HEIGHT_OFFSET = 1.5; // offset to make the platform spawn under your feet. Might need to be adjusted for unusually proportioned avatars. var USE_ENTITY_TIMEOUTS = true;var ENTITY_TIMEOUT_DURATION = 30.0; // kill entities in 30 secs if they don't get any updatesvar ENTITY_REFRESH_INTERVAL = 10.0; // poke the entities every 10s so they don't die until we're done with them // Initial statevar NUM_PLATFORM_ENTITIES = 400;var RADIUS = 5.0; // Defines min/max for onscreen platform radius, density, and entity width/height/depth sliders.// Color limits are hardcoded at [0, 255].var PLATFORM_RADIUS_RANGE  = [ 1.0, 15.0 ];var PLATFORM_DENSITY_RANGE = [ 0.0, 35.0 ]; // do NOT increase this above 40! (~20k limit). Entity count = Math.PI * radius * radius * density.var PLATFORM_SHAPE_DIMENSIONS_RANGE = [ 0.001, 2.0 ]; // axis-aligned entity dimension limits // Utils(function () {    if (typeof(Math.randRange) === 'undefined') {        Math.randRange = function (min, max) {            return Math.random() * (max - min) + min;        }    }    if (typeof(Math.randInt) === 'undefined') {        Math.randInt = function (n) {            return Math.floor(Math.random() * n) | 0;        }    }    function fromComponents (r, g, b, a) {        this.red   = r;        this.green = g;        this.blue  = b;        this.alpha = a || 1.0;    }    function fromHex (c) {        this.red   = parseInt(c[1] + c[2], 16);        this.green = parseInt(c[3] + c[4], 16);        this.blue  = parseInt(c[5] + c[6], 16);    }    var Color = this.Color = function () {        if (arguments.length >= 3) {            fromComponents.apply(this, arguments);        } else if (arguments.length == 1 && arguments[0].length == 7 && arguments[0][0] == '#') {            fromHex.apply(this, arguments);        } else {            throw new Error("Invalid arguments to new Color(): " + JSON.stringify(arguments));        }    }    Color.prototype.toString = function () {        return "[Color: " + JSON.stringify(this) + "]";    }})(); // RNG models(function () {    /// Encapsulates a simple color model that generates colors using a linear, pseudo-random color distribution.    var RandomColorModel = this.RandomColorModel = function () {        this.shadeRange = 0;    // = 200;        this.minColor = 55;     // = 100;        this.redRange = 255;    // = 200;        this.greenRange = 0;    // = 10;        this.blueRange = 0;     // = 0;    };    /// Generates 4 numbers in [0, 1] corresponding to each color attribute (uniform shade and additive red, green, blue).    /// This is done in a separate step from actually generating the colors, since it allows us to either A) completely    /// rebuild / re-randomize the color values, or B) reuse the RNG values but with different color parameters, which    /// enables us to do realtime color editing on the same visuals (awesome!).    RandomColorModel.prototype.generateSeed = function () {        return [ Math.random(), Math.random(), Math.random(), Math.random() ];    };    /// Takes a random 'seed' (4 floats from this.generateSeed()) and calculates a pseudo-random    /// color by combining that with the color model's current parameters.    RandomColorModel.prototype.getRandom = function (r) {        // logMessage("color seed values " + JSON.stringify(r));        var shade = Math.min(255, this.minColor + r[0] * this.shadeRange);         // No clamping on the color components, so they may overflow.         // However, this creates some pretty interesting visuals, so we're not "fixing" this.        var color = {            red:   shade + r[1] * this.redRange,            green: shade + r[2] * this.greenRange,            blue:  shade + r[3] * this.blueRange        };        // logMessage("this: " + JSON.stringify(this));        // logMessage("color: " + JSON.stringify(color), COLORS.RED);        return color;    };    /// Custom property iterator used to setup UI (sliders, etc)    RandomColorModel.prototype.setupUI = function (callback) {        var _this = this;        [             ['shadeRange', 'shade range'],             ['minColor', 'shade min'],             ['redRange', 'red (additive)'],            ['greenRange', 'green (additive)'],             ['blueRange', 'blue (additive)']         ].forEach(function (v) {            //       name, value, min, max, onValueChanged            callback(v[1], _this[v[0]], 0, 255, function (value) { _this[v[0]] = value });        });    }     /// Generates pseudo-random dimensions for our cubes / shapes.    var RandomShapeModel = this.RandomShapeModel = function () {        this.widthRange = [ 0.3, 0.7 ];        this.depthRange = [ 0.5, 0.8 ];        this.heightRange = [ 0.01, 0.08 ];    };    /// Generates 3 seed numbers in [0, 1]    RandomShapeModel.prototype.generateSeed = function () {        return [ Math.random(), Math.random(), Math.random() ];    }    /// Combines seed values with width/height/depth ranges to produce vec3 dimensions for a cube / sphere.    RandomShapeModel.prototype.getRandom = function (r) {        return {            x: r[0] * (this.widthRange[1] - this.widthRange[0]) + this.widthRange[0],            y: r[1] * (this.heightRange[1] - this.heightRange[0]) + this.heightRange[0],            z: r[2] * (this.depthRange[1] - this.depthRange[0]) + this.depthRange[0]        };    }    /// Custom property iterator used to setup UI (sliders, etc)    RandomShapeModel.prototype.setupUI = function (callback) {        var _this = this;        var dimensionsMin = PLATFORM_SHAPE_DIMENSIONS_RANGE[0];        var dimensionsMax = PLATFORM_SHAPE_DIMENSIONS_RANGE[1];        [            ['widthRange', 'width'],             ['depthRange', 'depth'],             ['heightRange', 'height']         ].forEach(function (v) {            //       name, value, min, max, onValueChanged            callback(v[1], _this[v[0]], dimensionsMin, dimensionsMax, function (value) { _this[v[0]] = value });        });    }     /// Combines color + shape PRNG models and hides their implementation details.    var RandomAttribModel = this.RandomAttribModel = function () {        this.colorModel = new RandomColorModel();        this.shapeModel = new RandomShapeModel();    }    /// Completely re-randomizes obj's `color` and `dimensions` parameters based on the current model params.    RandomAttribModel.prototype.randomizeShapeAndColor = function (obj) {        // logMessage("randomizing " + JSON.stringify(obj));        obj._colorSeed = this.colorModel.generateSeed();        obj._shapeSeed = this.shapeModel.generateSeed();        this.updateShapeAndColor(obj);        // logMessage("color seed: " + JSON.stringify(obj._colorSeed), COLORS.RED);        // logMessage("randomized color: " + JSON.stringify(obj.color), COLORS.RED);        // logMessage("randomized: " + JSON.stringify(obj));        return obj;    }    /// Updates obj's `color` and `dimensions` params to use the current model params.    /// Reuses hidden seed attribs; _must_ have called randomizeShapeAndColor(obj) at some point before    /// calling this.    RandomAttribModel.prototype.updateShapeAndColor = function (obj) {        try {            // logMessage("update shape and color: " + this.colorModel);            obj.color      = this.colorModel.getRandom(obj._colorSeed);            obj.dimensions = this.shapeModel.getRandom(obj._shapeSeed);         } catch (e) {            logMessage("update shape / color failed", COLORS.RED);            logMessage('' + e, COLORS.RED);            logMessage("obj._colorSeed = " + JSON.stringify(obj._colorSeed));            logMessage("obj._shapeSeed = " + JSON.stringify(obj._shapeSeed));            // logMessage("obj = " + JSON.stringify(obj));            throw e;        }        return obj;    }})(); // Status / logging UI (ignore this)(function () {    var COLORS = this.COLORS = {        'GREEN': new Color("#2D870C"),        'RED': new Color("#AF1E07"),        'LIGHT_GRAY': new Color("#CCCCCC"),        'DARK_GRAY': new Color("#4E4E4E")    };    function buildDebugLog () {        var LINE_WIDTH = 400;        var LINE_HEIGHT = 20;            var lines = [];        var lineIndex = 0;        for (var i = 0; i < NUM_DEBUG_LOG_LINES; ++i) {            lines.push(new UI.Label({                 text: " ", visible: false,                 width: LINE_WIDTH, height: LINE_HEIGHT,            }));        }        var title = new UI.Label({            text: SCRIPT_NAME, visible: true,            width: LINE_WIDTH, height: LINE_HEIGHT,        });            var overlay = new UI.Box({            visible: true,            width: LINE_WIDTH, height: 0,            backgroundColor: COLORS.DARK_GRAY,            backgroundAlpha: 0.3        });        overlay.setPosition(280, 10);        relayoutFrom(0);        UI.updateLayout();            function relayoutFrom (n) {            var layoutPos = {                x: overlay.position.x,                y: overlay.position.y            };                title.setPosition(layoutPos.x, layoutPos.y);            layoutPos.y += LINE_HEIGHT;                // for (var i = n; i >= 0; --i) {            for (var i = n + 1; i < lines.length; ++i) {                if (lines[i].visible) {                    lines[i].setPosition(layoutPos.x, layoutPos.y);                    layoutPos.y += LINE_HEIGHT;                }            }            // for (var i = lines.length - 1; i > n; --i) {            for (var i = 0; i <= n; ++i) {                if (lines[i].visible) {                    lines[i].setPosition(layoutPos.x, layoutPos.y);                    layoutPos.y += LINE_HEIGHT;                }            }            overlay.height = (layoutPos.y - overlay.position.y + 10);            overlay.getOverlay().update({                height: overlay.height            });        }        this.logMessage = function (text, color, alpha) {            lines[lineIndex].setVisible(true);            relayoutFrom(lineIndex);                lines[lineIndex].getOverlay().update({                text: text,                visible: true,                color: color || COLORS.LIGHT_GRAY,                alpha: alpha !== undefined ? alpha : 1.0,                x: lines[lineIndex].position.x,                y: lines[lineIndex].position.y            });            lineIndex = (lineIndex + 1) % lines.length;            UI.updateLayout();        }    }    if (USE_DEBUG_LOG) {        buildDebugLog();    } else {        this.logMessage = function (msg) {            print(SCRIPT_NAME + ": " + msg);        }    }})(); // Utils (ignore)(function () {    // Utility function    var withDefaults = this.withDefaults = function (properties, defaults) {        // logMessage("withDefaults: " + JSON.stringify(properties) + JSON.stringify(defaults));        properties = properties || {};        if (defaults) {            for (var k in defaults) {                properties[k] = defaults[k];            }        }        return properties;    }    var withReadonlyProp = this.withReadonlyProp = function (propname, value, obj) {        Object.defineProperty(obj, propname, {            value: value,            writable: false        });        return obj;    }     // Math utils    if (typeof(Math.randRange) === 'undefined') {        Math.randRange = function (min, max) {            return Math.random() * (max - min) + min;        }    }    if (typeof(Math.randInt) === 'undefined') {        Math.randInt = function (n) {            return Math.floor(Math.random() * n) | 0;        }    }     /// Random distrib: Get a random point within a circle on the xz plane with radius r, center p.    this.randomCirclePoint = function (r, pos) {        var a = Math.random(), b = Math.random();        if (b < a) {            var tmp = b;            b = a;            a = tmp;        }        var point = {            x: pos.x + b * r * Math.cos(2 * Math.PI * a / b),            y: pos.y,            z: pos.z + b * r * Math.sin(2 * Math.PI * a / b)        };        if (LOG_ENTITY_CREATION_MESSAGES) {            // logMessage("input params: " + JSON.stringify({ radius: r, position: pos }), COLORS.GREEN);            // logMessage("a = " + a + ", b = " + b);            logMessage("generated point: " + JSON.stringify(point), COLORS.RED);        }        return point;    }     // Entity utils. NOT using overlayManager for... reasons >.>    var makeEntity = this.makeEntity = function (properties) {        if (LOG_ENTITY_CREATION_MESSAGES) {            logMessage("Creating entity: " + JSON.stringify(properties));        }        var entity = Entities.addEntity(properties);        return withReadonlyProp("type", properties.type, {            update: function (properties) {                Entities.editEntity(entity, properties);            },            destroy: function () {                Entities.deleteEntity(entity)            },            getId: function () {                return entity;            }        });    }    // this.makeLight = function (properties) {    //  return makeEntity(withDefaults(properties, {    //      type: "Light",    //      isSpotlight: false,    //      diffuseColor: { red: 255, green: 100, blue: 100 },    //      ambientColor: { red: 200, green: 80, blue: 80 }    //  }));    // }    this.makeBox = function (properties) {        // logMessage("Creating box: " + JSON.stringify(properties));        return makeEntity(withDefaults(properties, {            type: "Box"        }));    }})(); // Platform(function () {    /// Encapsulates a platform 'piece'. Owns an entity (`box`), and handles destruction and some other state.    var PlatformComponent = this.PlatformComponent = function (properties) {        // logMessage("Platform component initialized with " + Object.keys(properties), COLORS.GREEN);        this.position   = properties.position || null;        this.color      = properties.color    || null;        this.dimensions = properties.dimensions || null;        this.entityType = properties.type || "Box";         // logMessage("Spawning with type: '" + this.entityType + "' (properties.type = '" + properties.type + "')", COLORS.GREEN);         if (properties._colorSeed)            this._colorSeed = properties._colorSeed;        if (properties._shapeSeed)            this._shapeSeed = properties._shapeSeed;         // logMessage("dimensions: " + JSON.stringify(this.dimensions));        // logMessage("color: " + JSON.stringify(this.color));         this.cachedEntity = null;        this.activeEntity = this.spawnEntity(this.entityType);    };    PlatformComponent.prototype.spawnEntity = function (type) {        return makeEntity({            type: type,            position: this.position,            dimensions: this.dimensions,            color: this.color,            lifetime: USE_ENTITY_TIMEOUTS ? ENTITY_TIMEOUT_DURATION : -1.0,            alpha: 0.5        });    }    if (USE_ENTITY_TIMEOUTS) {        PlatformComponent.prototype.pokeEntity = function () {            // Kinda inefficient, but there's no way to get around this :/            var age = Entities.getEntityProperties(this.activeEntity.getId()).age;            this.activeEntity.update({ lifetime: ENTITY_TIMEOUT_DURATION + age });        }    } else {        PlatformComponent.prototype.pokeEntity = function () {}    }    /// Updates platform to be at position p, and calls <entity>.update() with the current    /// position, color, and dimensions parameters.    PlatformComponent.prototype.update = function (position) {        if (position)            this.position = position;        // logMessage("updating with " + JSON.stringify(this));        this.activeEntity.update(this);    }    function swap (a, b) {        var tmp = a;        a = b;        b = tmp;    }       PlatformComponent.prototype.swapEntityType = function (newType) {        if (this.entityType !== newType) {            this.entityType = newType;            // logMessage("Destroying active entity and rebuilding it (newtype = '" + newType + "')");            if (this.activeEntity) {                this.activeEntity.destroy();            }            this.activeEntity = this.spawnEntity(newType);            // if (this.cachedEntity && this.cachedEntity.type == newType) {            //  this.cachedEntity.update({ visible: true });            //  this.activeEntity.update({ visible: false });            //  swap(this.cachedEntity, this.activeEntity);            //  this.update(this.position);            // } else {            //  this.activeEntity.update({ visible: false });            //  this.cachedEntity = this.activeEntity;            //  this.activeEntity = spawnEntity(newType);            // }        }    }    /// Swap state with another component    PlatformComponent.prototype.swap = function (other) {        swap(this.position, other.position);        swap(this.dimensions, other.dimensions);        swap(this.color, other.color);        swap(this.entityType, other.entityType);        swap(this.activeEntity, other.activeEntity);        swap(this._colorSeed, other._colorSeed);        swap(this._shapeSeed, other._shapeSeed);    }    PlatformComponent.prototype.destroy = function () {        if (this.activeEntity) {            this.activeEntity.destroy();            this.activeEntity = null;        }        if (this.cachedEntity) {            this.cachedEntity.destroy();            this.cachedEntity = null;        }    }     // util    function inRange (p1, p2, radius) {        return Vec3.distance(p1, p2) < Math.abs(radius);    }     /// Encapsulates a moving platform that follows the avatar around (mostly).    var DynamicPlatform = this.DynamicPlatform = function (n, position, radius) {        this.position = position;        this.radius   = radius;        this.randomizer = new RandomAttribModel();        this.boxType = "Box";        this.boxTypes = [ "Box", "Sphere" ];         logMessage("Spawning " + n + " entities", COLORS.GREEN);        var boxes = this.boxes = [];        while (n > 0) {            boxes.push(this.spawnEntity());            --n;        }        this.targetDensity = this.getEntityDensity();        this.pendingUpdates = {};        this.updateTimer = 0.0;

Lines 1–500 of 1221.