mirror of
https://github.com/overte-org/overte.git
synced 2025-04-07 09:52:24 +02:00
1221 lines
50 KiB
JavaScript
1221 lines
50 KiB
JavaScript
//
|
|
// 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;
|
|
|
|
this.platformHeight = position.y;
|
|
this.oldPos = { x: position.x, y: position.y, z: position.z };
|
|
this.oldRadius = radius;
|
|
|
|
// this.sendPokes();
|
|
}
|
|
DynamicPlatform.prototype.toString = function () {
|
|
return "[DynamicPlatform (" + this.boxes.length + " entities)]";
|
|
}
|
|
DynamicPlatform.prototype.spawnEntity = function () {
|
|
// logMessage("Called spawn entity. this.boxType = '" + this.boxType + "'")
|
|
var properties = { position: this.randomPoint(), type: this.boxType };
|
|
this.randomizer.randomizeShapeAndColor(properties);
|
|
return new PlatformComponent(properties);
|
|
}
|
|
DynamicPlatform.prototype.updateEntityAttribs = function () {
|
|
var _this = this;
|
|
this.setPendingUpdate('updateEntityAttribs', function () {
|
|
// logMessage("updating model", COLORS.GREEN);
|
|
_this.boxes.forEach(function (box) {
|
|
this.randomizer.updateShapeAndColor(box);
|
|
box.update();
|
|
}, _this);
|
|
});
|
|
}
|
|
DynamicPlatform.prototype.toggleBoxType = function () {
|
|
var _this = this;
|
|
this.setPendingUpdate('toggleBoxType', function () {
|
|
// Swap / cycle through types: find index of current type and set next type to idx+1
|
|
for (var idx = 0; idx < _this.boxTypes.length; ++idx) {
|
|
if (_this.boxTypes[idx] === _this.boxType) {
|
|
var nextIndex = (idx + 1) % _this.boxTypes.length;
|
|
logMessage("swapping box type from '" + _this.boxType + "' to '" + _this.boxTypes[nextIndex] + "'", COLORS.GREEN);
|
|
_this.boxType = _this.boxTypes[nextIndex];
|
|
break;
|
|
}
|
|
}
|
|
_this.boxes.forEach(function (box) {
|
|
box.swapEntityType(_this.boxType);
|
|
}, _this);
|
|
});
|
|
}
|
|
DynamicPlatform.prototype.getBoxType = function () {
|
|
return this.boxType;
|
|
}
|
|
|
|
// if (USE_ENTITY_TIMEOUTS) {
|
|
// DynamicPlatform.prototype.sendPokes = function () {
|
|
// var _this = this;
|
|
// function poke () {
|
|
// logMessage("Poking entities so they don't die", COLORS.GREEN);
|
|
// _this.boxes.forEach(function (box) {
|
|
// box.pokeEntity();
|
|
// }, _this);
|
|
|
|
|
|
// if (_this.pendingUpdates['keepalive']) {
|
|
// logMessage("previous timer: " + _this.pendingUpdates['keepalive'].timer + "; new timer: " + ENTITY_REFRESH_INTERVAL)
|
|
// }
|
|
// _this.pendingUpdates['keepalive'] = {
|
|
// callback: poke,
|
|
// timer: ENTITY_REFRESH_INTERVAL,
|
|
// skippedUpdates: 0
|
|
// };
|
|
// // _this.setPendingUpdate('keepalive', poke);
|
|
// // _this.pendingUpdates['keepalive'].timer = ENTITY_REFRESH_INTERVAL;
|
|
// }
|
|
// poke();
|
|
// }
|
|
// } else {
|
|
// DynamicPlatform.prototype.sendPokes = function () {};
|
|
// }
|
|
|
|
/// Queue impl that uses the update loop to limit potentially expensive updates to only execute every x seconds (default: 200 ms).
|
|
/// This is to prevent UI code from running full entity updates every 10 ms (or whatever).
|
|
DynamicPlatform.prototype.setPendingUpdate = function (name, callback) {
|
|
if (!this.pendingUpdates[name]) {
|
|
// logMessage("Queued update for " + name, COLORS.GREEN);
|
|
this.pendingUpdates[name] = {
|
|
callback: callback,
|
|
timer: 0.0,
|
|
skippedUpdates: 0
|
|
}
|
|
} else {
|
|
// logMessage("Deferred update for " + name, COLORS.GREEN);
|
|
this.pendingUpdates[name].callback = callback;
|
|
this.pendingUpdates[name].skippedUpdates++;
|
|
// logMessage("scheduling update for \"" + name + "\" to run in " + this.pendingUpdates[name].timer + " seconds");
|
|
}
|
|
}
|
|
/// Runs all queued updates as soon as they can execute (each one has a cooldown timer).
|
|
DynamicPlatform.prototype.processPendingUpdates = function (dt) {
|
|
for (var k in this.pendingUpdates) {
|
|
if (this.pendingUpdates[k].timer >= 0.0)
|
|
this.pendingUpdates[k].timer -= dt;
|
|
|
|
if (this.pendingUpdates[k].callback && this.pendingUpdates[k].timer < 0.0) {
|
|
// logMessage("Dispatching update for " + k);
|
|
try {
|
|
this.pendingUpdates[k].callback();
|
|
} catch (e) {
|
|
logMessage("update for \"" + k + "\" failed: " + e, COLORS.RED);
|
|
}
|
|
this.pendingUpdates[k].timer = MAX_UPDATE_INTERVAL;
|
|
this.pendingUpdates[k].skippedUpdates = 0;
|
|
this.pendingUpdates[k].callback = null;
|
|
} else {
|
|
// logMessage("Deferred update for " + k + " for " + this.pendingUpdates[k].timer + " seconds");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Updates the platform based on the avatar's current position (spawning / despawning entities as needed),
|
|
/// and calls processPendingUpdates() once this is done.
|
|
/// Does NOT have any update interval limits (it just updates every time it gets run), but these are not full
|
|
/// updates (they're incremental), so the network will not get flooded so long as the avatar is moving at a
|
|
/// normal walking / flying speed.
|
|
DynamicPlatform.prototype.updatePosition = function (dt, position) {
|
|
// logMessage("updating " + this);
|
|
position.y = this.platformHeight;
|
|
this.position = position;
|
|
|
|
var toUpdate = [];
|
|
this.boxes.forEach(function (box, i) {
|
|
// if (Math.abs(box.position.y - position.y) > HEIGHT_TOLERANCE || !inRange(box, position, radius)) {
|
|
if (!inRange(box.position, this.position, this.radius)) {
|
|
toUpdate.push(i);
|
|
}
|
|
}, this);
|
|
|
|
var MAX_TRIES = toUpdate.length * 8;
|
|
var tries = MAX_TRIES;
|
|
var moved = 0;
|
|
var recalcs = 0;
|
|
toUpdate.forEach(function (index) {
|
|
if ((index % 2 == 0) || tries > 0) {
|
|
do {
|
|
var randomPoint = this.randomPoint(this.position, this.radius);
|
|
++recalcs
|
|
} while (--tries > 0 && inRange(randomPoint, this.oldPos, this.oldRadiuss));
|
|
|
|
if (LOG_UPDATE_STATUS_MESSAGES && tries <= 0) {
|
|
logMessage("updatePlatform() gave up after " + MAX_TRIES + " iterations (" + moved + " / " + toUpdate.length + " successful updates)", COLORS.RED);
|
|
logMessage("old pos: " + JSON.stringify(this.oldPos) + ", old radius: " + this.oldRadius);
|
|
logMessage("new pos: " + JSON.stringify(this.position) + ", new radius: " + this.radius);
|
|
}
|
|
} else {
|
|
var randomPoint = this.randomPoint(position, this.radius);
|
|
}
|
|
|
|
this.randomizer.randomizeShapeAndColor(this.boxes[index]);
|
|
this.boxes[index].update(randomPoint);
|
|
// this.boxes[index].setValues({
|
|
// position: randomPoint,
|
|
// // dimensions: this.randomDimensions(),
|
|
// // color: this.randomColor()
|
|
// });
|
|
++moved;
|
|
}, this);
|
|
recalcs = recalcs - toUpdate.length;
|
|
|
|
this.oldPos = position;
|
|
this.oldRadius = this.radius;
|
|
if (LOG_UPDATE_STATUS_MESSAGES && toUpdate.length > 0) {
|
|
logMessage("updated " + toUpdate.length + " entities w/ " + recalcs + " recalcs");
|
|
}
|
|
}
|
|
|
|
DynamicPlatform.prototype.update = function (dt, position) {
|
|
this.updatePosition(dt, position);
|
|
this.processPendingUpdates(dt);
|
|
this.sendPokes(dt);
|
|
}
|
|
|
|
if (USE_ENTITY_TIMEOUTS) {
|
|
DynamicPlatform.prototype.sendPokes = function (dt) {
|
|
logMessage("starting keepalive", COLORS.GREEN);
|
|
// logMessage("dt = " + dt, COLORS.RED);
|
|
// var original = this.sendPokes;
|
|
var pokeTimer = 0.0;
|
|
this.sendPokes = function (dt) {
|
|
// logMessage("dt = " + dt);
|
|
if ((pokeTimer -= dt) < 0.0) {
|
|
// logMessage("Poking entities so they don't die", COLORS.GREEN);
|
|
this.boxes.forEach(function (box) {
|
|
box.pokeEntity();
|
|
}, this);
|
|
pokeTimer = ENTITY_REFRESH_INTERVAL;
|
|
} else {
|
|
// logMessage("Poking entities in " + pokeTimer + " seconds");
|
|
}
|
|
}
|
|
// logMessage("this.sendPokes === past this.sendPokes? " + (this.sendPokes === original), COLORS.GREEN);
|
|
this.sendPokes(dt);
|
|
}
|
|
} else {
|
|
DynamicPlatform.prototype.sendPokes = function () {};
|
|
}
|
|
DynamicPlatform.prototype.getEntityCount = function () {
|
|
return this.boxes.length;
|
|
}
|
|
DynamicPlatform.prototype.getEntityCountWithRadius = function (radius) {
|
|
var est = Math.floor((radius * radius) / (this.radius * this.radius) * this.getEntityCount());
|
|
var actual = Math.floor(Math.PI * radius * radius * this.getEntityDensity());
|
|
|
|
if (est != actual) {
|
|
logMessage("assert failed: getEntityCountWithRadius() -- est " + est + " != actual " + actual);
|
|
}
|
|
return est;
|
|
}
|
|
DynamicPlatform.prototype.getEntityCountWithDensity = function (density) {
|
|
return Math.floor(Math.PI * this.radius * this.radius * density);
|
|
}
|
|
|
|
/// Sets the entity count to n. Don't call this directly -- use setRadius / density instead.
|
|
DynamicPlatform.prototype.setEntityCount = function (n) {
|
|
if (n > this.boxes.length) {
|
|
// logMessage("Setting entity count to " + n + " (adding " + (n - this.boxes.length) + " entities)", COLORS.GREEN);
|
|
|
|
// Spawn new boxes
|
|
n = n - this.boxes.length;
|
|
for (; n > 0; --n) {
|
|
// var properties = { position: this.randomPoint() };
|
|
// this.randomizer.randomizeShapeAndColor(properties);
|
|
// this.boxes.push(new PlatformComponent(properties));
|
|
this.boxes.push(this.spawnEntity());
|
|
}
|
|
} else if (n < this.boxes.length) {
|
|
// logMessage("Setting entity count to " + n + " (removing " + (this.boxes.length - n) + " entities)", COLORS.GREEN);
|
|
|
|
// Destroy random boxes (technically, the most recent ones, but it should be sorta random)
|
|
n = this.boxes.length - n;
|
|
for (; n > 0; --n) {
|
|
this.boxes.pop().destroy();
|
|
}
|
|
}
|
|
}
|
|
/// Calculate the entity density based on radial surface area.
|
|
DynamicPlatform.prototype.getEntityDensity = function () {
|
|
return (this.boxes.length * 1.0) / (Math.PI * this.radius * this.radius);
|
|
}
|
|
/// Queues a setDensity update. This is expensive, so we don't call it directly from UI.
|
|
DynamicPlatform.prototype.setDensityOnNextUpdate = function (density) {
|
|
var _this = this;
|
|
this.targetDensity = density;
|
|
this.setPendingUpdate('density', function () {
|
|
_this.updateEntityDensity(density);
|
|
});
|
|
}
|
|
DynamicPlatform.prototype.updateEntityDensity = function (density) {
|
|
this.setEntityCount(Math.floor(density * Math.PI * this.radius * this.radius));
|
|
}
|
|
DynamicPlatform.prototype.getRadius = function () {
|
|
return this.radius;
|
|
}
|
|
/// Queues a setRadius update. This is expensive, so we don't call it directly from UI.
|
|
DynamicPlatform.prototype.setRadiusOnNextUpdate = function (radius) {
|
|
var _this = this;
|
|
this.setPendingUpdate('radius', function () {
|
|
_this.setRadius(radius);
|
|
});
|
|
}
|
|
var DEBUG_RADIUS_RECALC = false;
|
|
DynamicPlatform.prototype.setRadius = function (radius) {
|
|
if (radius < this.radius) { // Reduce case
|
|
// logMessage("Setting radius to " + radius + " (shrink by " + (this.radius - radius) + ")", COLORS.GREEN );
|
|
this.radius = radius;
|
|
|
|
// Remove all entities outside of current bounds. Requires swapping, since we want to maintain a contiguous array.
|
|
// Algorithm: two pointers at front and back. We traverse fwd and back, swapping elems so that all entities in bounds
|
|
// are at the front of the array, and all entities out of bounds are at the back. We then pop + destroy all entities
|
|
// at the back to reduce the entity count.
|
|
var count = this.boxes.length;
|
|
var toDelete = 0;
|
|
var swapList = [];
|
|
if (DEBUG_RADIUS_RECALC) {
|
|
logMessage("starting at i = 0, j = " + (count - 1));
|
|
}
|
|
for (var i = 0, j = count - 1; i < j; ) {
|
|
// Find first elem outside of bounds that we can move to the back
|
|
while (inRange(this.boxes[i].position, this.position, this.radius) && i < j) {
|
|
++i;
|
|
}
|
|
// Find first elem in bounds that we can move to the front
|
|
while (!inRange(this.boxes[j].position, this.position, this.radius) && i < j) {
|
|
--j; ++toDelete;
|
|
}
|
|
if (i < j) {
|
|
// swapList.push([i, j]);
|
|
if (DEBUG_RADIUS_RECALC) {
|
|
logMessage("swapping " + i + ", " + j);
|
|
}
|
|
this.boxes[i].swap(this.boxes[j]);
|
|
++i, --j; ++toDelete;
|
|
} else {
|
|
if (DEBUG_RADIUS_RECALC) {
|
|
logMessage("terminated at i = " + i + ", j = " + j, COLORS.RED);
|
|
}
|
|
}
|
|
}
|
|
if (DEBUG_RADIUS_RECALC) {
|
|
logMessage("toDelete = " + toDelete, COLORS.RED);
|
|
}
|
|
// Sanity check
|
|
if (toDelete > this.boxes.length) {
|
|
logMessage("Error: toDelete " + toDelete + " > entity count " + this.boxes.length + " (setRadius algorithm)", COLORS.RED);
|
|
toDelete = this.boxes.length;
|
|
}
|
|
if (toDelete > 0) {
|
|
// logMessage("Deleting " + toDelete + " entities as part of radius resize", COLORS.GREEN);
|
|
}
|
|
// Delete cleared boxes
|
|
for (; toDelete > 0; --toDelete) {
|
|
this.boxes.pop().destroy();
|
|
}
|
|
// fix entity density (just in case -- we may have uneven entity distribution)
|
|
this.updateEntityDensity(this.targetDensity);
|
|
} else if (radius > this.radius) {
|
|
// Grow case (much simpler)
|
|
// logMessage("Setting radius to " + radius + " (grow by " + (radius - this.radius) + ")", COLORS.GREEN);
|
|
|
|
// Add entities based on entity density
|
|
// var density = this.getEntityDensity();
|
|
var density = this.targetDensity;
|
|
var oldArea = Math.PI * this.radius * this.radius;
|
|
var n = Math.floor(density * Math.PI * (radius * radius - this.radius * this.radius));
|
|
|
|
if (n > 0) {
|
|
// logMessage("Adding " + n + " entities", COLORS.GREEN);
|
|
|
|
// Add entities (we use a slightly different algorithm to place them in the area between two concentric circles.
|
|
// This is *slightly* less uniform (the reason we're not using this everywhere is entities would be tightly clustered
|
|
// at the platform center and become spread out as the radius increases), but the use-case here is just incremental
|
|
// radius resizes and the user's not likely to notice the difference).
|
|
for (; n > 0; --n) {
|
|
var theta = Math.randRange(0.0, Math.PI * 2.0);
|
|
var r = Math.randRange(this.radius, radius);
|
|
// logMessage("theta = " + theta + ", r = " + r);
|
|
var pos = {
|
|
x: Math.cos(theta) * r + this.position.x,
|
|
y: this.position.y,
|
|
z: Math.sin(theta) * r + this.position.y
|
|
};
|
|
|
|
// var properties = { position: pos };
|
|
// this.randomizer.randomizeShapeAndColor(properties);
|
|
// this.boxes.push(new PlatformComponent(properties));
|
|
this.boxes.push(this.spawnEntity());
|
|
}
|
|
}
|
|
this.radius = radius;
|
|
}
|
|
}
|
|
DynamicPlatform.prototype.updateHeight = function (height) {
|
|
logMessage("Setting platform height to " + height);
|
|
this.platformHeight = height;
|
|
|
|
// Invalidate current boxes to trigger a rebuild
|
|
this.boxes.forEach(function (box) {
|
|
box.position.x += this.oldRadius * 100;
|
|
});
|
|
// this.update(dt, position, radius);
|
|
}
|
|
/// Gets a random point within the platform bounds.
|
|
/// Should maybe get moved to the RandomAttribModel (would be much cleaner), but this works for now.
|
|
DynamicPlatform.prototype.randomPoint = function (position, radius) {
|
|
position = position || this.position;
|
|
radius = radius !== undefined ? radius : this.radius;
|
|
return randomCirclePoint(radius, position);
|
|
}
|
|
/// Old. The RandomAttribModel replaces this and enables realtime editing of the *****_RANGE params.
|
|
// DynamicPlatform.prototype.randomDimensions = function () {
|
|
// return {
|
|
// x: Math.randRange(WIDTH_RANGE[0], WIDTH_RANGE[1]),
|
|
// y: Math.randRange(HEIGHT_RANGE[0], HEIGHT_RANGE[1]),
|
|
// z: Math.randRange(DEPTH_RANGE[0], DEPTH_RANGE[1])
|
|
// };
|
|
// }
|
|
// DynamicPlatform.prototype.randomColor = function () {
|
|
// var shade = Math.randRange(SHADE_RANGE[0], SHADE_RANGE[1]);
|
|
// // var h = HUE_RANGE;
|
|
// return {
|
|
// red: shade + Math.randRange(RED_RANGE[0], RED_RANGE[1]) | 0,
|
|
// green: shade + Math.randRange(GREEN_RANGE[0], GREEN_RANGE[1]) | 0,
|
|
// blue: shade + Math.randRange(BLUE_RANGE[0], BLUE_RANGE[1]) | 0
|
|
// }
|
|
// // return COLORS[Math.randInt(COLORS.length)]
|
|
// }
|
|
|
|
/// Cleanup.
|
|
DynamicPlatform.prototype.destroy = function () {
|
|
this.boxes.forEach(function (box) {
|
|
box.destroy();
|
|
});
|
|
this.boxes = [];
|
|
}
|
|
})();
|
|
|
|
// UI
|
|
(function () {
|
|
var CATCH_SETUP_ERRORS = true;
|
|
|
|
// Util functions for setting up widgets (the widget library is intended to be used like this)
|
|
function makePanel (dir, properties) {
|
|
return new UI.WidgetStack(withDefaults(properties, {
|
|
dir: dir
|
|
}));
|
|
}
|
|
function addSpacing (parent, width, height) {
|
|
parent.add(new UI.Box({
|
|
backgroundAlpha: 0.0,
|
|
width: width, height: height
|
|
}));
|
|
}
|
|
function addLabel (parent, text) {
|
|
return parent.add(new UI.Label({
|
|
text: text,
|
|
width: 200,
|
|
height: 20
|
|
}));
|
|
}
|
|
function addSlider (parent, label, min, max, getValue, onValueChanged) {
|
|
try {
|
|
var layout = parent.add(new UI.WidgetStack({ dir: "+x" }));
|
|
var textLabel = layout.add(new UI.Label({
|
|
text: label,
|
|
width: 130,
|
|
height: 20
|
|
}));
|
|
var valueLabel = layout.add(new UI.Label({
|
|
text: "" + (+getValue().toFixed(1)),
|
|
width: 60,
|
|
height: 20
|
|
}));
|
|
var slider = layout.add(new UI.Slider({
|
|
value: getValue(), minValue: min, maxValue: max,
|
|
width: 300, height: 20,
|
|
slider: {
|
|
width: 30,
|
|
height: 18
|
|
},
|
|
onValueChanged: function (value) {
|
|
valueLabel.setText("" + (+value.toFixed(1)));
|
|
onValueChanged(value, slider);
|
|
UI.updateLayout();
|
|
}
|
|
}));
|
|
return slider;
|
|
} catch (e) {
|
|
logMessage("" + e, COLORS.RED);
|
|
logMessage("parent: " + parent, COLORS.RED);
|
|
logMessage("label: " + label, COLORS.RED);
|
|
logMessage("min: " + min, COLORS.RED);
|
|
logMessage("max: " + max, COLORS.RED);
|
|
logMessage("getValue: " + getValue, COLORS.RED);
|
|
logMessage("onValueChanged: " + onValueChanged, COLORS.RED);
|
|
throw e;
|
|
}
|
|
}
|
|
function addButton (parent, label, onClicked) {
|
|
var button = parent.add(new UI.Box({
|
|
text: label,
|
|
width: 160,
|
|
height: 26,
|
|
leftMargin: 8,
|
|
topMargin: 3
|
|
}));
|
|
button.addAction('onClick', onClicked);
|
|
return button;
|
|
}
|
|
function moveToBottomLeftScreenCorner (widget) {
|
|
var border = 5;
|
|
var pos = {
|
|
x: border,
|
|
y: Controller.getViewportDimensions().y - widget.getHeight() - border
|
|
};
|
|
if (widget.position.x != pos.x || widget.position.y != pos.y) {
|
|
widget.setPosition(pos.x, pos.y);
|
|
UI.updateLayout();
|
|
}
|
|
}
|
|
var _export = this;
|
|
|
|
/// Setup the UI. Creates a bunch of sliders for setting the platform radius, density, and entity color / shape properties.
|
|
/// The entityCount slider is readonly.
|
|
function _setupUI (platform) {
|
|
var layoutContainer = makePanel("+y", { visible: false });
|
|
// layoutContainer.setPosition(10, 280);
|
|
// makeDraggable(layoutContainer);
|
|
_export.onScreenResize = function () {
|
|
moveToBottomLeftScreenCorner(layoutContainer);
|
|
}
|
|
var topSection = layoutContainer.add(makePanel("+x")); addSpacing(layoutContainer, 1, 5);
|
|
var btmSection = layoutContainer.add(makePanel("+x"));
|
|
|
|
var controls = topSection.add(makePanel("+y")); addSpacing(topSection, 20, 1);
|
|
var buttons = topSection.add(makePanel("+y")); addSpacing(topSection, 20, 1);
|
|
|
|
var colorControls = btmSection.add(makePanel("+y")); addSpacing(btmSection, 20, 1);
|
|
var shapeControls = btmSection.add(makePanel("+y")); addSpacing(btmSection, 20, 1);
|
|
|
|
// Top controls
|
|
addLabel(controls, "Platform (platform.js)");
|
|
controls.radiusSlider = addSlider(controls, "radius", PLATFORM_RADIUS_RANGE[0], PLATFORM_RADIUS_RANGE[1], function () { return platform.getRadius() },
|
|
function (value) {
|
|
platform.setRadiusOnNextUpdate(value);
|
|
controls.entityCountSlider.setValue(platform.getEntityCountWithRadius(value));
|
|
});
|
|
addSpacing(controls, 1, 2);
|
|
controls.densitySlider = addSlider(controls, "entity density", PLATFORM_DENSITY_RANGE[0], PLATFORM_DENSITY_RANGE[1], function () { return platform.getEntityDensity() },
|
|
function (value) {
|
|
platform.setDensityOnNextUpdate(value);
|
|
controls.entityCountSlider.setValue(platform.getEntityCountWithDensity(value));
|
|
});
|
|
addSpacing(controls, 1, 2);
|
|
|
|
var minEntities = Math.PI * PLATFORM_RADIUS_RANGE[0] * PLATFORM_RADIUS_RANGE[0] * PLATFORM_DENSITY_RANGE[0];
|
|
var maxEntities = Math.PI * PLATFORM_RADIUS_RANGE[1] * PLATFORM_RADIUS_RANGE[1] * PLATFORM_DENSITY_RANGE[1];
|
|
controls.entityCountSlider = addSlider(controls, "entity count", minEntities, maxEntities, function () { return platform.getEntityCount() },
|
|
function (value) {});
|
|
controls.entityCountSlider.actions = {}; // hack: make this slider readonly (clears all attached actions)
|
|
controls.entityCountSlider.slider.actions = {};
|
|
|
|
// Buttons
|
|
addSpacing(buttons, 1, 22);
|
|
addButton(buttons, 'rebuild', function () {
|
|
platform.updateHeight(MyAvatar.position.y - AVATAR_HEIGHT_OFFSET);
|
|
});
|
|
addSpacing(buttons, 1, 2);
|
|
addButton(buttons, 'toggle entity type', function () {
|
|
platform.toggleBoxType();
|
|
});
|
|
|
|
// Bottom controls
|
|
|
|
// Iterate over controls (making sliders) for the RNG shape / dimensions model
|
|
platform.randomizer.shapeModel.setupUI(function (name, value, min, max, setValue) {
|
|
// logMessage("platform.randomizer.shapeModel." + name + " = " + value);
|
|
var internal = {
|
|
avg: (value[0] + value[1]) * 0.5,
|
|
range: Math.abs(value[0] - value[1])
|
|
};
|
|
// logMessage(JSON.stringify(internal), COLORS.GREEN);
|
|
addSlider(shapeControls, name + ' avg', min, max, function () { return internal.avg; }, function (value) {
|
|
internal.avg = value;
|
|
setValue([ internal.avg - internal.range * 0.5, internal.avg + internal.range * 0.5 ]);
|
|
platform.updateEntityAttribs();
|
|
});
|
|
addSpacing(shapeControls, 1, 2);
|
|
addSlider(shapeControls, name + ' range', min, max, function () { return internal.range }, function (value) {
|
|
internal.range = value;
|
|
setValue([ internal.avg - internal.range * 0.5, internal.avg + internal.range * 0.5 ]);
|
|
platform.updateEntityAttribs();
|
|
});
|
|
addSpacing(shapeControls, 1, 2);
|
|
});
|
|
// Do the same for the color model
|
|
platform.randomizer.colorModel.setupUI(function (name, value, min, max, setValue) {
|
|
// logMessage("platform.randomizer.colorModel." + name + " = " + value);
|
|
addSlider(colorControls, name, min, max, function () { return value; }, function (value) {
|
|
setValue(value);
|
|
platform.updateEntityAttribs();
|
|
});
|
|
addSpacing(colorControls, 1, 2);
|
|
});
|
|
|
|
moveToBottomLeftScreenCorner(layoutContainer);
|
|
layoutContainer.setVisible(true);
|
|
}
|
|
this.setupUI = function (platform) {
|
|
if (CATCH_SETUP_ERRORS) {
|
|
try {
|
|
_setupUI(platform);
|
|
} catch (e) {
|
|
logMessage("Error setting up ui: " + e, COLORS.RED);
|
|
}
|
|
} else {
|
|
_setupUI(platform);
|
|
}
|
|
}
|
|
})();
|
|
|
|
// Error handling w/ explicit try / catch blocks. Good for catching unexpected errors with the onscreen debugLog
|
|
// (if it's enabled); bad for detailed debugging since you lose the file and line num even if the error gets rethrown.
|
|
|
|
// Catch errors from init
|
|
var CATCH_INIT_ERRORS = true;
|
|
|
|
// Catch errors from everything (technically, Script and Controller signals that runs platform / ui code)
|
|
var CATCH_ERRORS_FROM_EVENT_UPDATES = false;
|
|
|
|
// Setup everything
|
|
(function () {
|
|
var doLater = null;
|
|
if (CATCH_ERRORS_FROM_EVENT_UPDATES) {
|
|
// Decorates a function w/ explicit error catching + printing to the debug log.
|
|
function catchErrors (fcn) {
|
|
return function () {
|
|
try {
|
|
fcn.apply(this, arguments);
|
|
} catch (e) {
|
|
logMessage('' + e, COLORS.RED);
|
|
logMessage("while calling " + fcn);
|
|
logMessage("Called by: " + arguments.callee.caller);
|
|
}
|
|
}
|
|
}
|
|
// We need to do this after the functions are registered...
|
|
doLater = function () {
|
|
// Intercept errors from functions called by Script.update and Script.ScriptEnding.
|
|
[ 'teardown', 'startup', 'update', 'initPlatform', 'setupUI' ].forEach(function (fcn) {
|
|
this[fcn] = catchErrors(this[fcn]);
|
|
});
|
|
};
|
|
// These need to be wrapped first though:
|
|
|
|
// Intercept errors from UI functions called by Controller.****Event.
|
|
[ 'handleMousePress', 'handleMouseMove', 'handleMouseRelease' ].forEach(function (fcn) {
|
|
UI[fcn] = catchErrors(UI[fcn]);
|
|
});
|
|
}
|
|
|
|
function getTargetPlatformPosition () {
|
|
var pos = MyAvatar.position;
|
|
pos.y -= AVATAR_HEIGHT_OFFSET;
|
|
return pos;
|
|
}
|
|
|
|
// Program state
|
|
var platform = this.platform = null;
|
|
var lastHeight = null;
|
|
|
|
// Init
|
|
this.initPlatform = function () {
|
|
platform = new DynamicPlatform(NUM_PLATFORM_ENTITIES, getTargetPlatformPosition(), RADIUS);
|
|
lastHeight = getTargetPlatformPosition().y;
|
|
}
|
|
|
|
// Handle relative screen positioning (UI)
|
|
var lastDimensions = Controller.getViewportDimensions();
|
|
function checkScreenDimensions () {
|
|
var dimensions = Controller.getViewportDimensions();
|
|
if (dimensions.x != lastDimensions.x || dimensions.y != lastDimensions.y) {
|
|
onScreenResize(dimensions.x, dimensions.y);
|
|
}
|
|
lastDimensions = dimensions;
|
|
}
|
|
|
|
// Update
|
|
this.update = function (dt) {
|
|
checkScreenDimensions();
|
|
var pos = getTargetPlatformPosition();
|
|
platform.update(dt, getTargetPlatformPosition(), platform.getRadius());
|
|
}
|
|
|
|
// Teardown
|
|
this.teardown = function () {
|
|
try {
|
|
platform.destroy();
|
|
UI.teardown();
|
|
|
|
Controller.mousePressEvent.disconnect(UI.handleMousePress);
|
|
Controller.mouseMoveEvent.disconnect(UI.handleMouseMove);
|
|
Controller.mouseReleaseEvent.disconnect(UI.handleMouseRelease);
|
|
} catch (e) {
|
|
logMessage("" + e, COLORS.RED);
|
|
}
|
|
}
|
|
|
|
if (doLater) {
|
|
doLater();
|
|
}
|
|
|
|
// Delays startup until / if entities can be spawned.
|
|
this.startup = function (dt) {
|
|
if (Entities.canAdjustLocks() && Entities.canRez()) {
|
|
Script.update.disconnect(this.startup);
|
|
|
|
function init () {
|
|
logMessage("initializing...");
|
|
|
|
this.initPlatform();
|
|
|
|
Script.update.connect(this.update);
|
|
Script.scriptEnding.connect(this.teardown);
|
|
|
|
this.setupUI(platform);
|
|
|
|
logMessage("finished initializing.", COLORS.GREEN);
|
|
}
|
|
if (CATCH_INIT_ERRORS) {
|
|
try {
|
|
init();
|
|
} catch (error) {
|
|
logMessage("" + error, COLORS.RED);
|
|
}
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
Controller.mousePressEvent.connect(UI.handleMousePress);
|
|
Controller.mouseMoveEvent.connect(UI.handleMouseMove);
|
|
Controller.mouseReleaseEvent.connect(UI.handleMouseRelease);
|
|
} else {
|
|
if (!startup.printedWarnMsg) {
|
|
startup.timer = startup.timer || startup.ENTITY_SERVER_WAIT_TIME;
|
|
if ((startup.timer -= dt) < 0.0) {
|
|
logMessage("Waiting for entity server");
|
|
startup.printedWarnMsg = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
startup.ENTITY_SERVER_WAIT_TIME = 0.2; // print "waiting for entity server" if more than this time has elapsed in startup()
|
|
|
|
Script.update.connect(this.startup);
|
|
})();
|
|
|
|
})();
|