//// 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. Load more
// // 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 overlays Script.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 / sec var 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 updates var ENTITY_REFRESH_INTERVAL = 10.0; // poke the entities every 10s so they don't die until we're done with them // Initial state var 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;