early shadows

This commit is contained in:
Matei Adriel 2019-07-15 20:25:55 +03:00
parent 9eba227ec3
commit b5f3d9e4eb
17 changed files with 281 additions and 77 deletions

5
package-lock.json generated
View file

@ -5609,6 +5609,11 @@
"invert-kv": "^1.0.0"
}
},
"line-intersect": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/line-intersect/-/line-intersect-2.2.1.tgz",
"integrity": "sha512-uOPErCqtEnHYsnesl56XmKm9nWF27kOqZCuibJEkyAJ23FHsKmOHo8FPT6SRAp2h3wzvyXJNjbPsq9FF5x29vw=="
},
"load-json-file": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",

View file

@ -40,6 +40,7 @@
"dependencies": {
"@eix-js/utils": "0.0.6",
"deepmerge": "^4.0.0",
"line-intersect": "^2.2.1",
"mainloop.js": "^1.0.4",
"react": "^16.8.6",
"react-dom": "^16.8.6",

View file

@ -1,6 +1,7 @@
import { Singleton } from '@eix-js/utils'
import { Observable, fromEvent, BehaviorSubject } from 'rxjs'
import { map } from 'rxjs/operators'
import { multiply } from '../../vector2/helpers/basic'
@Singleton
export class Screen {
@ -25,4 +26,8 @@ export class Screen {
public get y() {
return this.height.value
}
public get center() {
return multiply([this.x, this.y], 0.5)
}
}

View file

@ -1,10 +1,9 @@
import { Transform, vector2 } from './Transform'
import { Transform } from './Transform'
export class Gate {
public static lastId = 0
public transform = new Transform()
public id = Gate.lastId++
public shadow: vector2 = [0, 0]
public constructor(public color = 'blue') {}
}

View file

@ -5,26 +5,17 @@ import { MouseEventInfo } from '../../core/components/FluidCanvas'
import { pointInSquare } from '../helpers/pointInSquare'
import { vector2 } from './Transform'
import merge from 'deepmerge'
import { smoothStep } from '../../vector2/helpers/smoothStep'
import { renderGate } from '../helpers/renderGate'
import { renderGateShadow } from '../helpers/renderGateShadow'
import { Gate } from './Gate'
import { MouseManager } from './MouseManager'
import { Screen } from '../../core/classes/Screen'
import {
add,
invert,
ofLength,
length,
multiply
} from '../../vector2/helpers/basic'
export interface SimulationRendererOptions {
shadows: {
enabled: boolean
color: string
offset: number
speed: number
lightHeight: number
gateHeight: number
}
dnd: {
rotation: number
@ -35,8 +26,8 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
shadows: {
enabled: true,
color: 'rgba(0,0,0,0.3)',
offset: 15,
speed: 1
gateHeight: 10,
lightHeight: 50
},
dnd: {
rotation: Math.PI / 12 // 7.5 degrees
@ -133,13 +124,22 @@ export class SimulationRenderer {
public render(ctx: CanvasRenderingContext2D) {
this.clear(ctx)
const center = this.screen.center
// render gates
for (const gate of this.simulation.gates) {
renderGate(ctx, gate)
if (this.options.shadows.enabled) {
renderGateShadow(ctx, this.options.shadows.color, gate)
renderGateShadow(
ctx,
this.options.shadows.color,
gate,
this.options.shadows.gateHeight,
[center[0], center[1], this.options.shadows.lightHeight]
)
}
renderGate(ctx, gate)
// renderGate(ctx, gate)
}
}
@ -152,32 +152,7 @@ export class SimulationRenderer {
return this.simulation.gates.get(id)
}
public getOptimalShadow(gate: Gate) {
const center = multiply([this.screen.x, this.screen.y] as vector2, 0.5)
const difference = add(center, invert(gate.transform.position))
return add(
add(difference, center),
ofLength(difference, this.options.shadows.offset)
)
}
public getShadowPosition(gate: Gate) {
return gate.transform.position.map(
(value, index) => value - this.getOptimalShadow(gate)[index]
) as vector2
}
public update(delta: number) {
for (const gate of this.simulation.gates) {
gate.shadow = smoothStep(
this.options.shadows.speed,
gate.shadow,
this.getShadowPosition(gate)
)
}
const selected = this.getSelected()
if (selected && this.movedSelection) {
@ -187,6 +162,10 @@ export class SimulationRenderer {
} else {
this.mouseManager.update()
}
// for (const gate of this.simulation.gates) {
// gate.transform.rotation += 0.01
// }
}
public getSelected() {

View file

@ -1,7 +1,20 @@
import { BehaviorSubject } from 'rxjs'
import { allCombinations } from '../helpers/allCombinations'
import { rotateAroundVector } from '../../vector2/helpers/rotate'
export type vector2 = [number, number]
export type vector3 = [number, number, number]
export type vector4 = [number, number, number, number]
export type vector8 = [
number,
number,
number,
number,
number,
number,
number,
number
]
export class Transform {
public constructor(
@ -14,6 +27,40 @@ export class Transform {
return [...this.position, ...this.scale] as vector4
}
public getPoints() {
const combinations = Array.from(allCombinations([0, 1], [0, 1]))
// those are not in the right order
const points = combinations.map(combination => [
this.x + this.height * combination[0],
this.y + this.width * combination[1]
])
const pointsInTheRightOrder = [
points[0],
points[1],
points[3],
points[2]
] as vector2[]
const result = pointsInTheRightOrder.map(point =>
rotateAroundVector(point, this.center, this.rotation)
) as vector2[]
return result
}
public getEdges() {
const points = this.getPoints()
const edges = []
for (let index = 0; index < points.length; index++) {
edges.push([points[index], points[(index + 1) % points.length]])
}
return edges as [vector2, vector2][]
}
/** Short forms for random stuff */
get x() {
@ -40,6 +87,10 @@ export class Transform {
return this.y + this.height
}
get center() {
return [this.x + this.width / 2, this.y + this.height / 2] as vector2
}
set x(value: number) {
this.position = [value, this.y]
}

View file

@ -0,0 +1,8 @@
export function* allCombinations<T>(first: T[], second: T[]): Iterable<[T, T]> {
for (const item of first) {
// TODO: change name
for (const element of second) {
yield [item, element]
}
}
}

View file

@ -0,0 +1,23 @@
import { vector2 } from '../classes/Transform'
export const drawPolygon = (
ctx: CanvasRenderingContext2D,
points: vector2[],
fill = true,
stroke = false
) => {
ctx.beginPath()
for (const point of points) {
ctx.lineTo(...point)
}
ctx.closePath()
if (fill) {
ctx.fill()
}
if (stroke) {
ctx.stroke()
}
}

View file

@ -2,30 +2,16 @@ import { Transform } from '../classes/Transform'
export const drawRotatedSquare = (
ctx: CanvasRenderingContext2D,
{ position, scale, rotation }: Transform,
rotationMode = 0
{ position, scale, rotation }: Transform
) => {
ctx.save()
ctx.translate(...position)
if (rotationMode === 0) {
ctx.translate(scale[0] / 2, scale[1] / 2)
} else if (rotationMode === 1) {
ctx.translate(scale[0], scale[1])
} else if (rotationMode === 1) {
ctx.translate(0, scale[1])
}
ctx.translate(scale[0] / 2, scale[1] / 2)
ctx.rotate(rotation)
if (rotationMode === 0) {
ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale)
} else if (rotationMode === 1) {
ctx.fillRect(-scale[0], -scale[1], ...scale)
} else if (rotationMode === -1) {
ctx.fillRect(0, 0, ...scale)
}
ctx.fillRect(scale[0] / -2, scale[1] / -2, ...scale)
ctx.restore()
}

View file

@ -0,0 +1,8 @@
import { vector3, vector2 } from '../classes/Transform'
export const projectPointOnPlane = (point: vector3, light: vector3) =>
point.slice(0, 2).map((position, index) => {
const delta = light[index] - position
return light[index] - (delta + (point[2] * delta) / light[2])
}) as vector2

View file

@ -1,13 +1,7 @@
import { Gate } from '../classes/Gate'
import { drawRotatedSquare } from './drawRotatedSquare'
import { MouseManager } from '../classes/MouseManager'
export const renderGate = (ctx: CanvasRenderingContext2D, gate: Gate) => {
let mode = 0
if (gate.transform.rotation > 0) mode = 1
else if (gate.transform.rotation < 0) mode = -1
ctx.fillStyle = gate.color
drawRotatedSquare(ctx, gate.transform, mode)
drawRotatedSquare(ctx, gate.transform)
}

View file

@ -1,19 +1,110 @@
import { Gate } from '../classes/Gate'
import { vector2, Transform } from '../classes/Transform'
import { clamp } from './clamp'
import { drawRotatedSquare } from './drawRotatedSquare'
import { projectPointOnPlane } from './projectPoint'
import { drawPolygon } from './drawPolygon'
import { vector3, vector2, vector4, vector8 } from '../classes/Transform'
import { checkIntersection } from 'line-intersect'
import { reverseArray } from './reverseArray'
import { length, add, invert } from '../../vector2/helpers/basic'
export const pointRecivesLight = (
points: vector2[], //this needs to have an even length
light: vector3,
index: number,
ctx: CanvasRenderingContext2D
) => {
const point = points[index]
const oposittePoint = points[(index + points.length / 2) % points.length]
const edgesToCheck = [
[oposittePoint, points[(index + 1) % points.length]],
[oposittePoint, points[index === 0 ? points.length - 1 : index - 1]]
].map(points => points.flat() as vector4)
for (const edge of edgesToCheck) {
const intersectionCheckParameters = [
[light[0], light[1]],
point,
edge
].flat() as vector8
const result = checkIntersection(...intersectionCheckParameters).type
if (result === 'intersecting') {
return false
}
}
return true
}
export const renderGateShadow = (
ctx: CanvasRenderingContext2D,
color: string,
gate: Gate
gate: Gate,
gateHeight: number,
light: vector3
) => {
const scale = gate.transform.scale
ctx.fillStyle = color
drawRotatedSquare(
ctx,
new Transform(gate.shadow, scale, gate.transform.rotation)
const points = gate.transform.getPoints()
const exposedPoints = points.filter((point, index) =>
pointRecivesLight(points, light, index, ctx)
)
let includedPoints = [...points]
if (exposedPoints.length === 3) {
let min = Infinity
let current: null | vector2 = null
for (const point of exposedPoints) {
const size = length(
add(point, invert(light.slice(0, 2) as vector2))
)
if (size < min) {
min = size
current = point
}
}
if (current) {
includedPoints.splice(includedPoints.indexOf(current), 1)
}
if (
includedPoints[0][1] < light[1] &&
includedPoints[1][1] < light[1] &&
!(includedPoints[2][1] > light[1])
) {
const temporary = includedPoints[0]
includedPoints[0] = includedPoints[1]
includedPoints[1] = temporary
}
}
if (exposedPoints.length === 2) {
includedPoints = points.filter(
point => exposedPoints.indexOf(point) === -1
)
}
const projections = includedPoints.map(point =>
// ts doesnt let me do [...point, gateHeight]
projectPointOnPlane([point[0], point[1], gateHeight], light)
)
const polygon = [includedPoints, reverseArray(projections)].flat()
drawPolygon(ctx, polygon)
ctx.fillStyle = 'red'
for (const point of [...includedPoints, ...projections, light]) {
ctx.beginPath()
ctx.ellipse(point[0], point[1], 10, 10, 0, 0, Math.PI * 2)
ctx.fill()
}
ctx.strokeStyle = 'yellow'
drawPolygon(ctx, points, false, true)
}

View file

@ -0,0 +1,10 @@
export const reverseArray = <T>(array: T[]) => {
const arr: T[] = []
for (let index = array.length - 1; index >= 0; index--) {
const element = array[index]
arr.push(element)
}
return arr
}

View file

@ -2,8 +2,15 @@ import { vector2 } from '../../simulation/classes/Transform'
// Basic stuff for arrays
export const add = (first: vector2, second: vector2) =>
first.map((value, index) => value + second[index]) as vector2
// If i don't say vector2 as the type adnotation
// ts will throw some errors (because this is recursive)
export const add = (...vectors: vector2[]): vector2 => {
const first = vectors[0]
const others = vectors.slice(1)
const othersSum = others.length > 1 ? add(...others) : others[0]
return first.map((value, index) => value + othersSum[index]) as vector2
}
export const invert = (vector: vector2) => vector.map(val => -val) as vector2

View file

@ -0,0 +1,22 @@
import { vector2 } from '../../simulation/classes/Transform'
import { add, invert } from './basic'
const { cos, sin } = Math
export const rotate = (vector: vector2, angle: number): vector2 => {
const x = cos(angle) * vector[0] - sin(angle) * vector[1]
const y = sin(angle) * vector[0] + cos(angle) * vector[1]
return [x, y]
}
export const rotateAroundVector = (
vector: vector2,
around: vector2,
angle: number
) => {
const translated = add(vector, invert(around))
const rotated = rotate(translated, angle)
return add(rotated, around)
}

View file

@ -1,5 +1,7 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "*": ["types/*"] },
"moduleResolution": "node",
"esModuleInterop": true,
"jsx": "preserve",

13
types/line-intersect.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
export const checkIntersection: (
x1: number,
y1: number,
x2: number,
y2: number,
x3: number,
y3: number,
x4: number,
y4: number
) => {
type: 'colinear' | 'parallel' | 'none' | 'intersecting'
}
export const colinearPointWithinSegment: any