From 241330442c1575bc69dcceab4df590cf6f66d746 Mon Sep 17 00:00:00 2001 From: Eric Levin Date: Thu, 4 Jun 2015 17:35:26 -0700 Subject: [PATCH] added paint.js and line rider script. Users can paint lines with hydra or mouse, and then go on a ride over the line they just created. --- examples/lineRider.js | 117 +++++++++++ examples/paint.js | 478 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 595 insertions(+) create mode 100644 examples/lineRider.js create mode 100644 examples/paint.js diff --git a/examples/lineRider.js b/examples/lineRider.js new file mode 100644 index 0000000000..21a690fc63 --- /dev/null +++ b/examples/lineRider.js @@ -0,0 +1,117 @@ +// +// lineRider.js +// examples +// +// Created by Eric Levin on 6/4/15. +// Copyright 2014 High Fidelity, Inc. +// +// Takes the avatar on a line ride. Meant to be used in conjunction with paint.js +// Paint a line and then click on roller coaster icon to start! +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +LineRider = function() { + HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + var screenSize = Controller.getViewportDimensions(); + + var BUTTON_SIZE = 32; + var PADDING = 3; + + this.buttonOffColor = { + red: 250, + green: 10, + blue: 10 + }; + this.buttonOnColor = { + red: 10, + green: 200, + blue: 100 + }; + this.riding = false; + + this.startButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE + PADDING * 2, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/coaster.png?v2", + color: this.buttonOffColor, + alpha: 1 + }); + + this.currentPoint = 0; + this.shouldUpdate = false; + this.moveIntervalTime = 50; + +} + + +LineRider.prototype.move = function() { + if (!this.shouldMove) { + return; + } + MyAvatar.position = this.points[this.currentPoint++]; + + if (this.currentPoint === this.points.length) { + this.currentPoint = 0; + } + var self = this; + Script.setTimeout(function() { + self.move(); + }, this.moveIntervalTime); +} + +LineRider.prototype.setPath = function(points) { + this.points = points; +} + +LineRider.prototype.addStartHandler = function(callback) { + this.onStart = callback; +} + + +LineRider.prototype.mousePressEvent = function(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == this.startButton) { + this.toggleRide(); + + } + +} + +LineRider.prototype.toggleRide = function() { + this.riding = !this.riding; + if (this.riding === true) { + Overlays.editOverlay(this.startButton, { + color: this.buttonOnColor + }); + if (this.onStart) { + this.onStart(); + //make sure we actually have a path + if (this.points.length > 2) { + this.shouldMove = true; + } + var self = this; + Script.setTimeout(function() { + self.move(); + }, this.moveIntervalTime); + } + } else { + Overlays.editOverlay(this.startButton, { + color: this.buttonOffColor + }) + this.shouldMove = false; + } + +} +LineRider.prototype.startRide = function() { + this.shouldUpdate = true; + +} + +LineRider.prototype.cleanup = function() { + Overlays.deleteOverlay(this.startButton); +} \ No newline at end of file diff --git a/examples/paint.js b/examples/paint.js new file mode 100644 index 0000000000..9a774a1442 --- /dev/null +++ b/examples/paint.js @@ -0,0 +1,478 @@ +// +// paint.js +// examples +// +// Created by Eric Levin on 6/4/15. +// Copyright 2014 High Fidelity, Inc. +// +// This script allows you to paint with the hydra or mouse! +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +Script.include('lineRider.js') +var MAX_POINTS_PER_LINE = 30; +var DRAWING_DISTANCE = 5; + +var colorPalette = [{ + red: 236, + green: 208, + blue: 120 +}, { + red: 217, + green: 91, + blue: 67 +}, { + red: 192, + green: 41, + blue: 66 +}, { + red: 84, + green: 36, + blue: 55 +}, { + red: 83, + green: 119, + blue: 122 +}]; + +var currentColorIndex = 0; +var currentColor = colorPalette[currentColorIndex]; + + + +if (hydraCheck() === true) { + HydraPaint(); +} else { + MousePaint(); +} + + +function cycleColor() { + currentColor = colorPalette[++currentColorIndex]; + if (currentColorIndex === colorPalette.length - 1) { + currentColorIndex = -1; + } + +} + +function hydraCheck() { + var numberOfButtons = Controller.getNumberOfButtons(); + var numberOfTriggers = Controller.getNumberOfTriggers(); + var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); + var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + hydrasConnected = (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2); + return hydrasConnected; //hydrasConnected; +} + + +function MousePaint() { + var lines = []; + var isDrawing = false; + var path = []; + + var lineRider = new LineRider(); + lineRider.addStartHandler(function() { + var points = []; + //create points array from list of all points in path + path.forEach(function(point) { + points.push(point); + }); + lineRider.setPath(points); + }); + + + + var LINE_WIDTH = 7; + var line; + var points = []; + + + var BRUSH_SIZE = 0.08; + + var brush = Entities.addEntity({ + type: 'Sphere', + position: { + x: 0, + y: 0, + z: 0 + }, + color: currentColor, + dimensions: { + x: BRUSH_SIZE, + y: BRUSH_SIZE, + z: BRUSH_SIZE + } + }); + + + function newLine(point) { + line = Entities.addEntity({ + position: MyAvatar.position, + type: "Line", + color: currentColor, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + lineWidth: LINE_WIDTH + }); + points = []; + if (point) { + points.push(point); + path.push(point); + } + lines.push(line); + } + + + function mouseMoveEvent(event) { + if (!isDrawing) { + return; + } + + + var pickRay = Camera.computePickRay(event.x, event.y); + var addVector = Vec3.multiply(Vec3.normalize(pickRay.direction), DRAWING_DISTANCE); + var point = Vec3.sum(Camera.getPosition(), addVector); + points.push(point); + Entities.editEntity(line, { + linePoints: points + }); + Entities.editEntity(brush, { + position: point + }); + path.push(point); + + if (points.length === MAX_POINTS_PER_LINE) { + //We need to start a new line! + newLine(point); + } + } + + function mousePressEvent(event) { + lineRider.mousePressEvent(event); + path = []; + newLine(); + isDrawing = true; + + + } + + function mouseReleaseEvent() { + isDrawing = false; + } + + function keyPressEvent(event) { + if (event.text === "SPACE") { + cycleColor(); + Entities.editEntity(brush, { + color: currentColor + }); + } + } + + function cleanup() { + lines.forEach(function(line) { + Entities.deleteEntity(line); + }); + Entities.deleteEntity(brush); + + } + + + Controller.mousePressEvent.connect(mousePressEvent); + Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + Controller.mouseMoveEvent.connect(mouseMoveEvent); + Script.scriptEnding.connect(cleanup); + + function randFloat(low, high) { + return Math.floor(low + Math.random() * (high - low)); + } + + + function randInt(low, high) { + return Math.floor(randFloat(low, high)); + } + + Controller.keyPressEvent.connect(keyPressEvent); +} + + + +//*****************HYDRA PAINT ******************************************* + + + + +function HydraPaint() { + + + + var lineRider = new LineRider(); + lineRider.addStartHandler(function() { + var points = []; + //create points array from list of all points in path + rightController.path.forEach(function(point) { + points.push(point); + }); + lineRider.setPath(points); + }); + + var LEFT = 0; + var RIGHT = 1; + + var currentTime = 0; + + + var DISTANCE_FROM_HAND = 2; + var minBrushSize = .02; + var maxBrushSize = .04 + + + var minLineWidth = 5; + var maxLineWidth = 10; + var currentLineWidth = minLineWidth; + var MIN_PAINT_TRIGGER_THRESHOLD = .01; + var LINE_LIFETIME = 20; + var COLOR_CHANGE_TIME_FACTOR = 0.1; + + var RIGHT_BUTTON_1 = 7 + var RIGHT_BUTTON_2 = 8 + var RIGHT_BUTTON_3 = 9; + var RIGHT_BUTTON_4 = 10 + + var LEFT_BUTTON_1 = 1; + var LEFT_BUTTON_2 = 2; + var LEFT_BUTTON_3 = 3; + var LEFT_BUTTON_4 = 4; + + var STROKE_SMOOTH_FACTOR = 1; + + var MIN_DRAW_DISTANCE = 1; + var MAX_DRAW_DISTANCE = 2; + + function controller(side, undoButton, redoButton, cycleColorButton, startRideButton) { + this.triggerHeld = false; + this.triggerThreshold = 0.9; + this.side = side; + this.palm = 2 * side; + this.tip = 2 * side + 1; + this.trigger = side; + this.lines = []; + this.deletedLines = [] //just an array of properties objects + this.isPainting = false; + + this.undoButton = undoButton; + this.undoButtonPressed = false; + this.prevUndoButtonPressed = false; + + this.redoButton = redoButton; + this.redoButtonPressed = false; + this.prevRedoButtonPressed = false; + + this.cycleColorButton = cycleColorButton; + this.cycleColorButtonPressed = false; + this.prevColorCycleButtonPressed = false; + + this.startRideButton = startRideButton; + this.startRideButtonPressed = false; + this.prevStartRideButtonPressed = false; + + this.strokeCount = 0; + this.currentBrushSize = minBrushSize; + this.points = []; + this.path = []; + + this.brush = Entities.addEntity({ + type: 'Sphere', + position: { + x: 0, + y: 0, + z: 0 + }, + color: currentColor, + dimensions: { + x: minBrushSize, + y: minBrushSize, + z: minBrushSize + } + }); + + + this.newLine = function(point) { + this.line = Entities.addEntity({ + position: MyAvatar.position, + type: "Line", + color: currentColor, + dimensions: { + x: 10, + y: 10, + z: 10 + }, + lineWidth: 5, + // lifetime: LINE_LIFETIME + }); + this.points = []; + if (point) { + this.points.push(point); + this.path.push(point); + } + this.lines.push(this.line); + } + + this.update = function(deltaTime) { + this.updateControllerState(); + this.avatarPalmOffset = Vec3.subtract(this.palmPosition, MyAvatar.position); + this.projectedForwardDistance = Vec3.dot(Quat.getFront(Camera.getOrientation()), this.avatarPalmOffset); + this.mappedPalmOffset = map(this.projectedForwardDistance, -.5, .5, MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); + this.tipDirection = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)); + this.offsetVector = Vec3.multiply(this.mappedPalmOffset, this.tipDirection); + this.drawPoint = Vec3.sum(this.palmPosition, this.offsetVector); + this.currentBrushSize = map(this.triggerValue, 0, 1, minBrushSize, maxBrushSize); + Entities.editEntity(this.brush, { + position: this.drawPoint, + dimensions: { + x: this.currentBrushSize, + y: this.currentBrushSize, + z: this.currentBrushSize + }, + color: currentColor + }); + if (this.triggerValue > MIN_PAINT_TRIGGER_THRESHOLD) { + if (!this.isPainting) { + this.isPainting = true; + this.newLine(); + this.path = []; + } + if (this.strokeCount % STROKE_SMOOTH_FACTOR === 0) { + this.paint(this.drawPoint); + } + this.strokeCount++; + } else if (this.triggerValue < MIN_PAINT_TRIGGER_THRESHOLD && this.isPainting) { + this.releaseTrigger(); + } + + this.oldPalmPosition = this.palmPosition; + this.oldTipPosition = this.tipPosition; + } + + this.releaseTrigger = function() { + this.isPainting = false; + + } + + + this.updateControllerState = function() { + this.undoButtonPressed = Controller.isButtonPressed(this.undoButton); + this.redoButtonPressed = Controller.isButtonPressed(this.redoButton); + this.cycleColorButtonPressed = Controller.isButtonPressed(this.cycleColorButton); + this.startRideButtonPressed = Controller.isButtonPressed(this.startRideButton); + + //This logic gives us button release + if (this.prevUndoButtonPressed === true && this.undoButtonPressed === false) { + //User released undo button, so undo + this.undoStroke(); + } + if (this.prevRedoButtonPressed === true && this.redoButtonPressed === false) { + this.redoStroke(); + } + + if (this.prevCycleColorButtonPressed === true && this.cycleColorButtonPressed === false) { + cycleColor(); + Entities.editEntity(this.brush, { + color: currentColor + }); + } + if (this.prevStartRideButtonPressed === true && this.startRideButtonPressed === false) { + lineRider.toggleRide(); + } + this.prevRedoButtonPressed = this.redoButtonPressed; + this.prevUndoButtonPressed = this.undoButtonPressed; + this.prevCycleColorButtonPressed = this.cycleColorButtonPressed; + this.prevStartRideButtonPressed = this.startRideButtonPressed; + + this.palmPosition = Controller.getSpatialControlPosition(this.palm); + this.tipPosition = Controller.getSpatialControlPosition(this.tip); + this.triggerValue = Controller.getTriggerValue(this.trigger); + } + + this.undoStroke = function() { + var deletedLine = this.lines.pop(); + var deletedLineProps = Entities.getEntityProperties(deletedLine); + this.deletedLines.push(deletedLineProps); + Entities.deleteEntity(deletedLine); + } + + this.redoStroke = function() { + var restoredLine = Entities.addEntity(this.deletedLines.pop()); + Entities.addEntity(restoredLine); + this.lines.push(restoredLine); + } + + this.paint = function(point) { + + currentLineWidth = map(this.triggerValue, 0, 1, minLineWidth, maxLineWidth); + this.points.push(point); + this.path.push(point); + Entities.editEntity(this.line, { + linePoints: this.points, + lineWidth: currentLineWidth, + color: this.rgbColor + }); + if (this.points.length > MAX_POINTS_PER_LINE) { + this.newLine(point); + } + } + + this.cleanup = function() { + Entities.deleteEntity(this.brush); + this.lines.forEach(function(line) { + Entities.deleteEntity(line); + }); + } + } + + function update(deltaTime) { + rightController.update(deltaTime); + leftController.update(deltaTime); + currentTime += deltaTime; + } + + function scriptEnding() { + rightController.cleanup(); + leftController.cleanup(); + lineRider.cleanup(); + } + + function mousePressEvent(event) { + lineRider.mousePressEvent(event); + } + + function vectorIsZero(v) { + return v.x === 0 && v.y === 0 && v.z === 0; + } + + + var rightController = new controller(RIGHT, RIGHT_BUTTON_3, RIGHT_BUTTON_4, RIGHT_BUTTON_1, RIGHT_BUTTON_2); + var leftController = new controller(LEFT, LEFT_BUTTON_3, LEFT_BUTTON_4, LEFT_BUTTON_1, LEFT_BUTTON_2); + + Script.update.connect(update); + Script.scriptEnding.connect(scriptEnding); + Controller.mousePressEvent.connect(mousePressEvent); + + function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); + } + + function randFloat(low, high) { + return Math.floor(low + Math.random() * (high - low)); + } + + + function randInt(low, high) { + return Math.floor(randFloat(low, high)); + } +} \ No newline at end of file